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        SelectedFormatter,
   31    },
   32    tree_sitter_python,
   33};
   34use language_settings::Formatter;
   35use lsp::CompletionParams;
   36use multi_buffer::{IndentGuide, PathKey};
   37use parking_lot::Mutex;
   38use pretty_assertions::{assert_eq, assert_ne};
   39use project::{
   40    FakeFs,
   41    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   42    project_settings::LspSettings,
   43};
   44use serde_json::{self, json};
   45use settings::{
   46    AllLanguageSettingsContent, IndentGuideBackgroundColoring, IndentGuideColoring,
   47    ProjectSettingsContent,
   48};
   49use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   50use std::{
   51    iter,
   52    sync::atomic::{self, AtomicUsize},
   53};
   54use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
   55use text::ToPoint as _;
   56use unindent::Unindent;
   57use util::{
   58    assert_set_eq, path,
   59    rel_path::rel_path,
   60    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   61    uri,
   62};
   63use workspace::{
   64    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   65    OpenOptions, ViewId,
   66    invalid_buffer_view::InvalidBufferView,
   67    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   68    register_project_item,
   69};
   70
   71#[gpui::test]
   72fn test_edit_events(cx: &mut TestAppContext) {
   73    init_test(cx, |_| {});
   74
   75    let buffer = cx.new(|cx| {
   76        let mut buffer = language::Buffer::local("123456", cx);
   77        buffer.set_group_interval(Duration::from_secs(1));
   78        buffer
   79    });
   80
   81    let events = Rc::new(RefCell::new(Vec::new()));
   82    let editor1 = cx.add_window({
   83        let events = events.clone();
   84        |window, cx| {
   85            let entity = cx.entity();
   86            cx.subscribe_in(
   87                &entity,
   88                window,
   89                move |_, _, event: &EditorEvent, _, _| match event {
   90                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   91                    EditorEvent::BufferEdited => {
   92                        events.borrow_mut().push(("editor1", "buffer edited"))
   93                    }
   94                    _ => {}
   95                },
   96            )
   97            .detach();
   98            Editor::for_buffer(buffer.clone(), None, window, cx)
   99        }
  100    });
  101
  102    let editor2 = cx.add_window({
  103        let events = events.clone();
  104        |window, cx| {
  105            cx.subscribe_in(
  106                &cx.entity(),
  107                window,
  108                move |_, _, event: &EditorEvent, _, _| match event {
  109                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  110                    EditorEvent::BufferEdited => {
  111                        events.borrow_mut().push(("editor2", "buffer edited"))
  112                    }
  113                    _ => {}
  114                },
  115            )
  116            .detach();
  117            Editor::for_buffer(buffer.clone(), None, window, cx)
  118        }
  119    });
  120
  121    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  122
  123    // Mutating editor 1 will emit an `Edited` event only for that editor.
  124    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  125    assert_eq!(
  126        mem::take(&mut *events.borrow_mut()),
  127        [
  128            ("editor1", "edited"),
  129            ("editor1", "buffer edited"),
  130            ("editor2", "buffer edited"),
  131        ]
  132    );
  133
  134    // Mutating editor 2 will emit an `Edited` event only for that editor.
  135    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  136    assert_eq!(
  137        mem::take(&mut *events.borrow_mut()),
  138        [
  139            ("editor2", "edited"),
  140            ("editor1", "buffer edited"),
  141            ("editor2", "buffer edited"),
  142        ]
  143    );
  144
  145    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  146    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  147    assert_eq!(
  148        mem::take(&mut *events.borrow_mut()),
  149        [
  150            ("editor1", "edited"),
  151            ("editor1", "buffer edited"),
  152            ("editor2", "buffer edited"),
  153        ]
  154    );
  155
  156    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  157    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  158    assert_eq!(
  159        mem::take(&mut *events.borrow_mut()),
  160        [
  161            ("editor1", "edited"),
  162            ("editor1", "buffer edited"),
  163            ("editor2", "buffer edited"),
  164        ]
  165    );
  166
  167    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  168    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  169    assert_eq!(
  170        mem::take(&mut *events.borrow_mut()),
  171        [
  172            ("editor2", "edited"),
  173            ("editor1", "buffer edited"),
  174            ("editor2", "buffer edited"),
  175        ]
  176    );
  177
  178    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  179    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  180    assert_eq!(
  181        mem::take(&mut *events.borrow_mut()),
  182        [
  183            ("editor2", "edited"),
  184            ("editor1", "buffer edited"),
  185            ("editor2", "buffer edited"),
  186        ]
  187    );
  188
  189    // No event is emitted when the mutation is a no-op.
  190    _ = editor2.update(cx, |editor, window, cx| {
  191        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  192            s.select_ranges([0..0])
  193        });
  194
  195        editor.backspace(&Backspace, window, cx);
  196    });
  197    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  198}
  199
  200#[gpui::test]
  201fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  202    init_test(cx, |_| {});
  203
  204    let mut now = Instant::now();
  205    let group_interval = Duration::from_millis(1);
  206    let buffer = cx.new(|cx| {
  207        let mut buf = language::Buffer::local("123456", cx);
  208        buf.set_group_interval(group_interval);
  209        buf
  210    });
  211    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  212    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  213
  214    _ = editor.update(cx, |editor, window, cx| {
  215        editor.start_transaction_at(now, window, cx);
  216        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  217            s.select_ranges([2..4])
  218        });
  219
  220        editor.insert("cd", window, cx);
  221        editor.end_transaction_at(now, cx);
  222        assert_eq!(editor.text(cx), "12cd56");
  223        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  224
  225        editor.start_transaction_at(now, window, cx);
  226        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  227            s.select_ranges([4..5])
  228        });
  229        editor.insert("e", window, cx);
  230        editor.end_transaction_at(now, cx);
  231        assert_eq!(editor.text(cx), "12cde6");
  232        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  233
  234        now += group_interval + Duration::from_millis(1);
  235        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  236            s.select_ranges([2..2])
  237        });
  238
  239        // Simulate an edit in another editor
  240        buffer.update(cx, |buffer, cx| {
  241            buffer.start_transaction_at(now, cx);
  242            buffer.edit([(0..1, "a")], None, cx);
  243            buffer.edit([(1..1, "b")], None, cx);
  244            buffer.end_transaction_at(now, cx);
  245        });
  246
  247        assert_eq!(editor.text(cx), "ab2cde6");
  248        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  249
  250        // Last transaction happened past the group interval in a different editor.
  251        // Undo it individually and don't restore selections.
  252        editor.undo(&Undo, window, cx);
  253        assert_eq!(editor.text(cx), "12cde6");
  254        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  255
  256        // First two transactions happened within the group interval in this editor.
  257        // Undo them together and restore selections.
  258        editor.undo(&Undo, window, cx);
  259        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  260        assert_eq!(editor.text(cx), "123456");
  261        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  262
  263        // Redo the first two transactions together.
  264        editor.redo(&Redo, window, cx);
  265        assert_eq!(editor.text(cx), "12cde6");
  266        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  267
  268        // Redo the last transaction on its own.
  269        editor.redo(&Redo, window, cx);
  270        assert_eq!(editor.text(cx), "ab2cde6");
  271        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  272
  273        // Test empty transactions.
  274        editor.start_transaction_at(now, window, cx);
  275        editor.end_transaction_at(now, cx);
  276        editor.undo(&Undo, window, cx);
  277        assert_eq!(editor.text(cx), "12cde6");
  278    });
  279}
  280
  281#[gpui::test]
  282fn test_ime_composition(cx: &mut TestAppContext) {
  283    init_test(cx, |_| {});
  284
  285    let buffer = cx.new(|cx| {
  286        let mut buffer = language::Buffer::local("abcde", cx);
  287        // Ensure automatic grouping doesn't occur.
  288        buffer.set_group_interval(Duration::ZERO);
  289        buffer
  290    });
  291
  292    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  293    cx.add_window(|window, cx| {
  294        let mut editor = build_editor(buffer.clone(), window, cx);
  295
  296        // Start a new IME composition.
  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        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  300        assert_eq!(editor.text(cx), "äbcde");
  301        assert_eq!(
  302            editor.marked_text_ranges(cx),
  303            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  304        );
  305
  306        // Finalize IME composition.
  307        editor.replace_text_in_range(None, "ā", window, cx);
  308        assert_eq!(editor.text(cx), "ābcde");
  309        assert_eq!(editor.marked_text_ranges(cx), None);
  310
  311        // IME composition edits are grouped and are undone/redone at once.
  312        editor.undo(&Default::default(), window, cx);
  313        assert_eq!(editor.text(cx), "abcde");
  314        assert_eq!(editor.marked_text_ranges(cx), None);
  315        editor.redo(&Default::default(), window, cx);
  316        assert_eq!(editor.text(cx), "ābcde");
  317        assert_eq!(editor.marked_text_ranges(cx), None);
  318
  319        // Start a new IME composition.
  320        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  321        assert_eq!(
  322            editor.marked_text_ranges(cx),
  323            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  324        );
  325
  326        // Undoing during an IME composition cancels it.
  327        editor.undo(&Default::default(), window, cx);
  328        assert_eq!(editor.text(cx), "ābcde");
  329        assert_eq!(editor.marked_text_ranges(cx), None);
  330
  331        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  332        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  333        assert_eq!(editor.text(cx), "ābcdè");
  334        assert_eq!(
  335            editor.marked_text_ranges(cx),
  336            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  337        );
  338
  339        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  340        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  341        assert_eq!(editor.text(cx), "ābcdę");
  342        assert_eq!(editor.marked_text_ranges(cx), None);
  343
  344        // Start a new IME composition with multiple cursors.
  345        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  346            s.select_ranges([
  347                OffsetUtf16(1)..OffsetUtf16(1),
  348                OffsetUtf16(3)..OffsetUtf16(3),
  349                OffsetUtf16(5)..OffsetUtf16(5),
  350            ])
  351        });
  352        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  353        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  354        assert_eq!(
  355            editor.marked_text_ranges(cx),
  356            Some(vec![
  357                OffsetUtf16(0)..OffsetUtf16(3),
  358                OffsetUtf16(4)..OffsetUtf16(7),
  359                OffsetUtf16(8)..OffsetUtf16(11)
  360            ])
  361        );
  362
  363        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  364        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  365        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  366        assert_eq!(
  367            editor.marked_text_ranges(cx),
  368            Some(vec![
  369                OffsetUtf16(1)..OffsetUtf16(2),
  370                OffsetUtf16(5)..OffsetUtf16(6),
  371                OffsetUtf16(9)..OffsetUtf16(10)
  372            ])
  373        );
  374
  375        // Finalize IME composition with multiple cursors.
  376        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  377        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  378        assert_eq!(editor.marked_text_ranges(cx), None);
  379
  380        editor
  381    });
  382}
  383
  384#[gpui::test]
  385fn test_selection_with_mouse(cx: &mut TestAppContext) {
  386    init_test(cx, |_| {});
  387
  388    let editor = cx.add_window(|window, cx| {
  389        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  390        build_editor(buffer, window, cx)
  391    });
  392
  393    _ = editor.update(cx, |editor, window, cx| {
  394        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  395    });
  396    assert_eq!(
  397        editor
  398            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  399            .unwrap(),
  400        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  401    );
  402
  403    _ = editor.update(cx, |editor, window, cx| {
  404        editor.update_selection(
  405            DisplayPoint::new(DisplayRow(3), 3),
  406            0,
  407            gpui::Point::<f32>::default(),
  408            window,
  409            cx,
  410        );
  411    });
  412
  413    assert_eq!(
  414        editor
  415            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  416            .unwrap(),
  417        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  418    );
  419
  420    _ = editor.update(cx, |editor, window, cx| {
  421        editor.update_selection(
  422            DisplayPoint::new(DisplayRow(1), 1),
  423            0,
  424            gpui::Point::<f32>::default(),
  425            window,
  426            cx,
  427        );
  428    });
  429
  430    assert_eq!(
  431        editor
  432            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  433            .unwrap(),
  434        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  435    );
  436
  437    _ = editor.update(cx, |editor, window, cx| {
  438        editor.end_selection(window, cx);
  439        editor.update_selection(
  440            DisplayPoint::new(DisplayRow(3), 3),
  441            0,
  442            gpui::Point::<f32>::default(),
  443            window,
  444            cx,
  445        );
  446    });
  447
  448    assert_eq!(
  449        editor
  450            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  451            .unwrap(),
  452        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  453    );
  454
  455    _ = editor.update(cx, |editor, window, cx| {
  456        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  457        editor.update_selection(
  458            DisplayPoint::new(DisplayRow(0), 0),
  459            0,
  460            gpui::Point::<f32>::default(),
  461            window,
  462            cx,
  463        );
  464    });
  465
  466    assert_eq!(
  467        editor
  468            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  469            .unwrap(),
  470        [
  471            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  472            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  473        ]
  474    );
  475
  476    _ = editor.update(cx, |editor, window, cx| {
  477        editor.end_selection(window, cx);
  478    });
  479
  480    assert_eq!(
  481        editor
  482            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  483            .unwrap(),
  484        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  485    );
  486}
  487
  488#[gpui::test]
  489fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  490    init_test(cx, |_| {});
  491
  492    let editor = cx.add_window(|window, cx| {
  493        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  494        build_editor(buffer, window, cx)
  495    });
  496
  497    _ = editor.update(cx, |editor, window, cx| {
  498        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  499    });
  500
  501    _ = editor.update(cx, |editor, window, cx| {
  502        editor.end_selection(window, cx);
  503    });
  504
  505    _ = editor.update(cx, |editor, window, cx| {
  506        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  507    });
  508
  509    _ = editor.update(cx, |editor, window, cx| {
  510        editor.end_selection(window, cx);
  511    });
  512
  513    assert_eq!(
  514        editor
  515            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  516            .unwrap(),
  517        [
  518            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  519            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  520        ]
  521    );
  522
  523    _ = editor.update(cx, |editor, window, cx| {
  524        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  525    });
  526
  527    _ = editor.update(cx, |editor, window, cx| {
  528        editor.end_selection(window, cx);
  529    });
  530
  531    assert_eq!(
  532        editor
  533            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  534            .unwrap(),
  535        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  536    );
  537}
  538
  539#[gpui::test]
  540fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  541    init_test(cx, |_| {});
  542
  543    let editor = cx.add_window(|window, cx| {
  544        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  545        build_editor(buffer, window, cx)
  546    });
  547
  548    _ = editor.update(cx, |editor, window, cx| {
  549        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  550        assert_eq!(
  551            editor.selections.display_ranges(cx),
  552            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  553        );
  554    });
  555
  556    _ = editor.update(cx, |editor, window, cx| {
  557        editor.update_selection(
  558            DisplayPoint::new(DisplayRow(3), 3),
  559            0,
  560            gpui::Point::<f32>::default(),
  561            window,
  562            cx,
  563        );
  564        assert_eq!(
  565            editor.selections.display_ranges(cx),
  566            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  567        );
  568    });
  569
  570    _ = editor.update(cx, |editor, window, cx| {
  571        editor.cancel(&Cancel, window, cx);
  572        editor.update_selection(
  573            DisplayPoint::new(DisplayRow(1), 1),
  574            0,
  575            gpui::Point::<f32>::default(),
  576            window,
  577            cx,
  578        );
  579        assert_eq!(
  580            editor.selections.display_ranges(cx),
  581            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  582        );
  583    });
  584}
  585
  586#[gpui::test]
  587fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  588    init_test(cx, |_| {});
  589
  590    let editor = cx.add_window(|window, cx| {
  591        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  592        build_editor(buffer, window, cx)
  593    });
  594
  595    _ = editor.update(cx, |editor, window, cx| {
  596        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  597        assert_eq!(
  598            editor.selections.display_ranges(cx),
  599            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  600        );
  601
  602        editor.move_down(&Default::default(), window, cx);
  603        assert_eq!(
  604            editor.selections.display_ranges(cx),
  605            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  606        );
  607
  608        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  609        assert_eq!(
  610            editor.selections.display_ranges(cx),
  611            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  612        );
  613
  614        editor.move_up(&Default::default(), window, cx);
  615        assert_eq!(
  616            editor.selections.display_ranges(cx),
  617            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  618        );
  619    });
  620}
  621
  622#[gpui::test]
  623fn test_clone(cx: &mut TestAppContext) {
  624    init_test(cx, |_| {});
  625
  626    let (text, selection_ranges) = marked_text_ranges(
  627        indoc! {"
  628            one
  629            two
  630            threeˇ
  631            four
  632            fiveˇ
  633        "},
  634        true,
  635    );
  636
  637    let editor = cx.add_window(|window, cx| {
  638        let buffer = MultiBuffer::build_simple(&text, cx);
  639        build_editor(buffer, window, cx)
  640    });
  641
  642    _ = editor.update(cx, |editor, window, cx| {
  643        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  644            s.select_ranges(selection_ranges.clone())
  645        });
  646        editor.fold_creases(
  647            vec![
  648                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  649                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  650            ],
  651            true,
  652            window,
  653            cx,
  654        );
  655    });
  656
  657    let cloned_editor = editor
  658        .update(cx, |editor, _, cx| {
  659            cx.open_window(Default::default(), |window, cx| {
  660                cx.new(|cx| editor.clone(window, cx))
  661            })
  662        })
  663        .unwrap()
  664        .unwrap();
  665
  666    let snapshot = editor
  667        .update(cx, |e, window, cx| e.snapshot(window, cx))
  668        .unwrap();
  669    let cloned_snapshot = cloned_editor
  670        .update(cx, |e, window, cx| e.snapshot(window, cx))
  671        .unwrap();
  672
  673    assert_eq!(
  674        cloned_editor
  675            .update(cx, |e, _, cx| e.display_text(cx))
  676            .unwrap(),
  677        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  678    );
  679    assert_eq!(
  680        cloned_snapshot
  681            .folds_in_range(0..text.len())
  682            .collect::<Vec<_>>(),
  683        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  684    );
  685    assert_set_eq!(
  686        cloned_editor
  687            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  688            .unwrap(),
  689        editor
  690            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  691            .unwrap()
  692    );
  693    assert_set_eq!(
  694        cloned_editor
  695            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  696            .unwrap(),
  697        editor
  698            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  699            .unwrap()
  700    );
  701}
  702
  703#[gpui::test]
  704async fn test_navigation_history(cx: &mut TestAppContext) {
  705    init_test(cx, |_| {});
  706
  707    use workspace::item::Item;
  708
  709    let fs = FakeFs::new(cx.executor());
  710    let project = Project::test(fs, [], cx).await;
  711    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  712    let pane = workspace
  713        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  714        .unwrap();
  715
  716    _ = workspace.update(cx, |_v, window, cx| {
  717        cx.new(|cx| {
  718            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  719            let mut editor = build_editor(buffer, window, cx);
  720            let handle = cx.entity();
  721            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  722
  723            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  724                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  725            }
  726
  727            // Move the cursor a small distance.
  728            // Nothing is added to the navigation history.
  729            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  730                s.select_display_ranges([
  731                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  732                ])
  733            });
  734            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  735                s.select_display_ranges([
  736                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  737                ])
  738            });
  739            assert!(pop_history(&mut editor, cx).is_none());
  740
  741            // Move the cursor a large distance.
  742            // The history can jump back to the previous position.
  743            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  744                s.select_display_ranges([
  745                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  746                ])
  747            });
  748            let nav_entry = pop_history(&mut editor, cx).unwrap();
  749            editor.navigate(nav_entry.data.unwrap(), window, cx);
  750            assert_eq!(nav_entry.item.id(), cx.entity_id());
  751            assert_eq!(
  752                editor.selections.display_ranges(cx),
  753                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  754            );
  755            assert!(pop_history(&mut editor, cx).is_none());
  756
  757            // Move the cursor a small distance via the mouse.
  758            // Nothing is added to the navigation history.
  759            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  760            editor.end_selection(window, cx);
  761            assert_eq!(
  762                editor.selections.display_ranges(cx),
  763                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  764            );
  765            assert!(pop_history(&mut editor, cx).is_none());
  766
  767            // Move the cursor a large distance via the mouse.
  768            // The history can jump back to the previous position.
  769            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  770            editor.end_selection(window, cx);
  771            assert_eq!(
  772                editor.selections.display_ranges(cx),
  773                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  774            );
  775            let nav_entry = pop_history(&mut editor, cx).unwrap();
  776            editor.navigate(nav_entry.data.unwrap(), window, cx);
  777            assert_eq!(nav_entry.item.id(), cx.entity_id());
  778            assert_eq!(
  779                editor.selections.display_ranges(cx),
  780                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  781            );
  782            assert!(pop_history(&mut editor, cx).is_none());
  783
  784            // Set scroll position to check later
  785            editor.set_scroll_position(gpui::Point::<f64>::new(5.5, 5.5), window, cx);
  786            let original_scroll_position = editor.scroll_manager.anchor();
  787
  788            // Jump to the end of the document and adjust scroll
  789            editor.move_to_end(&MoveToEnd, window, cx);
  790            editor.set_scroll_position(gpui::Point::<f64>::new(-2.5, -0.5), window, cx);
  791            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  792
  793            let nav_entry = pop_history(&mut editor, cx).unwrap();
  794            editor.navigate(nav_entry.data.unwrap(), window, cx);
  795            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  796
  797            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  798            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  799            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  800            let invalid_point = Point::new(9999, 0);
  801            editor.navigate(
  802                Box::new(NavigationData {
  803                    cursor_anchor: invalid_anchor,
  804                    cursor_position: invalid_point,
  805                    scroll_anchor: ScrollAnchor {
  806                        anchor: invalid_anchor,
  807                        offset: Default::default(),
  808                    },
  809                    scroll_top_row: invalid_point.row,
  810                }),
  811                window,
  812                cx,
  813            );
  814            assert_eq!(
  815                editor.selections.display_ranges(cx),
  816                &[editor.max_point(cx)..editor.max_point(cx)]
  817            );
  818            assert_eq!(
  819                editor.scroll_position(cx),
  820                gpui::Point::new(0., editor.max_point(cx).row().as_f64())
  821            );
  822
  823            editor
  824        })
  825    });
  826}
  827
  828#[gpui::test]
  829fn test_cancel(cx: &mut TestAppContext) {
  830    init_test(cx, |_| {});
  831
  832    let editor = cx.add_window(|window, cx| {
  833        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  834        build_editor(buffer, window, cx)
  835    });
  836
  837    _ = editor.update(cx, |editor, window, cx| {
  838        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  839        editor.update_selection(
  840            DisplayPoint::new(DisplayRow(1), 1),
  841            0,
  842            gpui::Point::<f32>::default(),
  843            window,
  844            cx,
  845        );
  846        editor.end_selection(window, cx);
  847
  848        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  849        editor.update_selection(
  850            DisplayPoint::new(DisplayRow(0), 3),
  851            0,
  852            gpui::Point::<f32>::default(),
  853            window,
  854            cx,
  855        );
  856        editor.end_selection(window, cx);
  857        assert_eq!(
  858            editor.selections.display_ranges(cx),
  859            [
  860                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  861                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  862            ]
  863        );
  864    });
  865
  866    _ = editor.update(cx, |editor, window, cx| {
  867        editor.cancel(&Cancel, window, cx);
  868        assert_eq!(
  869            editor.selections.display_ranges(cx),
  870            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  871        );
  872    });
  873
  874    _ = editor.update(cx, |editor, window, cx| {
  875        editor.cancel(&Cancel, window, cx);
  876        assert_eq!(
  877            editor.selections.display_ranges(cx),
  878            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  879        );
  880    });
  881}
  882
  883#[gpui::test]
  884fn test_fold_action(cx: &mut TestAppContext) {
  885    init_test(cx, |_| {});
  886
  887    let editor = cx.add_window(|window, cx| {
  888        let buffer = MultiBuffer::build_simple(
  889            &"
  890                impl Foo {
  891                    // Hello!
  892
  893                    fn a() {
  894                        1
  895                    }
  896
  897                    fn b() {
  898                        2
  899                    }
  900
  901                    fn c() {
  902                        3
  903                    }
  904                }
  905            "
  906            .unindent(),
  907            cx,
  908        );
  909        build_editor(buffer, window, cx)
  910    });
  911
  912    _ = editor.update(cx, |editor, window, cx| {
  913        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  914            s.select_display_ranges([
  915                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  916            ]);
  917        });
  918        editor.fold(&Fold, window, cx);
  919        assert_eq!(
  920            editor.display_text(cx),
  921            "
  922                impl Foo {
  923                    // Hello!
  924
  925                    fn a() {
  926                        1
  927                    }
  928
  929                    fn b() {⋯
  930                    }
  931
  932                    fn c() {⋯
  933                    }
  934                }
  935            "
  936            .unindent(),
  937        );
  938
  939        editor.fold(&Fold, window, cx);
  940        assert_eq!(
  941            editor.display_text(cx),
  942            "
  943                impl Foo {⋯
  944                }
  945            "
  946            .unindent(),
  947        );
  948
  949        editor.unfold_lines(&UnfoldLines, window, cx);
  950        assert_eq!(
  951            editor.display_text(cx),
  952            "
  953                impl Foo {
  954                    // Hello!
  955
  956                    fn a() {
  957                        1
  958                    }
  959
  960                    fn b() {⋯
  961                    }
  962
  963                    fn c() {⋯
  964                    }
  965                }
  966            "
  967            .unindent(),
  968        );
  969
  970        editor.unfold_lines(&UnfoldLines, window, cx);
  971        assert_eq!(
  972            editor.display_text(cx),
  973            editor.buffer.read(cx).read(cx).text()
  974        );
  975    });
  976}
  977
  978#[gpui::test]
  979fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  980    init_test(cx, |_| {});
  981
  982    let editor = cx.add_window(|window, cx| {
  983        let buffer = MultiBuffer::build_simple(
  984            &"
  985                class Foo:
  986                    # Hello!
  987
  988                    def a():
  989                        print(1)
  990
  991                    def b():
  992                        print(2)
  993
  994                    def c():
  995                        print(3)
  996            "
  997            .unindent(),
  998            cx,
  999        );
 1000        build_editor(buffer, window, cx)
 1001    });
 1002
 1003    _ = editor.update(cx, |editor, window, cx| {
 1004        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1005            s.select_display_ranges([
 1006                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1007            ]);
 1008        });
 1009        editor.fold(&Fold, window, cx);
 1010        assert_eq!(
 1011            editor.display_text(cx),
 1012            "
 1013                class Foo:
 1014                    # Hello!
 1015
 1016                    def a():
 1017                        print(1)
 1018
 1019                    def b():⋯
 1020
 1021                    def c():⋯
 1022            "
 1023            .unindent(),
 1024        );
 1025
 1026        editor.fold(&Fold, window, cx);
 1027        assert_eq!(
 1028            editor.display_text(cx),
 1029            "
 1030                class Foo:⋯
 1031            "
 1032            .unindent(),
 1033        );
 1034
 1035        editor.unfold_lines(&UnfoldLines, window, cx);
 1036        assert_eq!(
 1037            editor.display_text(cx),
 1038            "
 1039                class Foo:
 1040                    # Hello!
 1041
 1042                    def a():
 1043                        print(1)
 1044
 1045                    def b():⋯
 1046
 1047                    def c():⋯
 1048            "
 1049            .unindent(),
 1050        );
 1051
 1052        editor.unfold_lines(&UnfoldLines, window, cx);
 1053        assert_eq!(
 1054            editor.display_text(cx),
 1055            editor.buffer.read(cx).read(cx).text()
 1056        );
 1057    });
 1058}
 1059
 1060#[gpui::test]
 1061fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1062    init_test(cx, |_| {});
 1063
 1064    let editor = cx.add_window(|window, cx| {
 1065        let buffer = MultiBuffer::build_simple(
 1066            &"
 1067                class Foo:
 1068                    # Hello!
 1069
 1070                    def a():
 1071                        print(1)
 1072
 1073                    def b():
 1074                        print(2)
 1075
 1076
 1077                    def c():
 1078                        print(3)
 1079
 1080
 1081            "
 1082            .unindent(),
 1083            cx,
 1084        );
 1085        build_editor(buffer, window, cx)
 1086    });
 1087
 1088    _ = editor.update(cx, |editor, window, cx| {
 1089        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1090            s.select_display_ranges([
 1091                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1092            ]);
 1093        });
 1094        editor.fold(&Fold, window, cx);
 1095        assert_eq!(
 1096            editor.display_text(cx),
 1097            "
 1098                class Foo:
 1099                    # Hello!
 1100
 1101                    def a():
 1102                        print(1)
 1103
 1104                    def b():⋯
 1105
 1106
 1107                    def c():⋯
 1108
 1109
 1110            "
 1111            .unindent(),
 1112        );
 1113
 1114        editor.fold(&Fold, window, cx);
 1115        assert_eq!(
 1116            editor.display_text(cx),
 1117            "
 1118                class Foo:⋯
 1119
 1120
 1121            "
 1122            .unindent(),
 1123        );
 1124
 1125        editor.unfold_lines(&UnfoldLines, window, cx);
 1126        assert_eq!(
 1127            editor.display_text(cx),
 1128            "
 1129                class Foo:
 1130                    # Hello!
 1131
 1132                    def a():
 1133                        print(1)
 1134
 1135                    def b():⋯
 1136
 1137
 1138                    def c():⋯
 1139
 1140
 1141            "
 1142            .unindent(),
 1143        );
 1144
 1145        editor.unfold_lines(&UnfoldLines, window, cx);
 1146        assert_eq!(
 1147            editor.display_text(cx),
 1148            editor.buffer.read(cx).read(cx).text()
 1149        );
 1150    });
 1151}
 1152
 1153#[gpui::test]
 1154fn test_fold_at_level(cx: &mut TestAppContext) {
 1155    init_test(cx, |_| {});
 1156
 1157    let editor = cx.add_window(|window, cx| {
 1158        let buffer = MultiBuffer::build_simple(
 1159            &"
 1160                class Foo:
 1161                    # Hello!
 1162
 1163                    def a():
 1164                        print(1)
 1165
 1166                    def b():
 1167                        print(2)
 1168
 1169
 1170                class Bar:
 1171                    # World!
 1172
 1173                    def a():
 1174                        print(1)
 1175
 1176                    def b():
 1177                        print(2)
 1178
 1179
 1180            "
 1181            .unindent(),
 1182            cx,
 1183        );
 1184        build_editor(buffer, window, cx)
 1185    });
 1186
 1187    _ = editor.update(cx, |editor, window, cx| {
 1188        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1189        assert_eq!(
 1190            editor.display_text(cx),
 1191            "
 1192                class Foo:
 1193                    # Hello!
 1194
 1195                    def a():⋯
 1196
 1197                    def b():⋯
 1198
 1199
 1200                class Bar:
 1201                    # World!
 1202
 1203                    def a():⋯
 1204
 1205                    def b():⋯
 1206
 1207
 1208            "
 1209            .unindent(),
 1210        );
 1211
 1212        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1213        assert_eq!(
 1214            editor.display_text(cx),
 1215            "
 1216                class Foo:⋯
 1217
 1218
 1219                class Bar:⋯
 1220
 1221
 1222            "
 1223            .unindent(),
 1224        );
 1225
 1226        editor.unfold_all(&UnfoldAll, window, cx);
 1227        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1228        assert_eq!(
 1229            editor.display_text(cx),
 1230            "
 1231                class Foo:
 1232                    # Hello!
 1233
 1234                    def a():
 1235                        print(1)
 1236
 1237                    def b():
 1238                        print(2)
 1239
 1240
 1241                class Bar:
 1242                    # World!
 1243
 1244                    def a():
 1245                        print(1)
 1246
 1247                    def b():
 1248                        print(2)
 1249
 1250
 1251            "
 1252            .unindent(),
 1253        );
 1254
 1255        assert_eq!(
 1256            editor.display_text(cx),
 1257            editor.buffer.read(cx).read(cx).text()
 1258        );
 1259        let (_, positions) = marked_text_ranges(
 1260            &"
 1261                       class Foo:
 1262                           # Hello!
 1263
 1264                           def a():
 1265                              print(1)
 1266
 1267                           def b():
 1268                               p«riˇ»nt(2)
 1269
 1270
 1271                       class Bar:
 1272                           # World!
 1273
 1274                           def a():
 1275                               «ˇprint(1)
 1276
 1277                           def b():
 1278                               print(2)»
 1279
 1280
 1281                   "
 1282            .unindent(),
 1283            true,
 1284        );
 1285
 1286        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 1287            s.select_ranges(positions)
 1288        });
 1289
 1290        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1291        assert_eq!(
 1292            editor.display_text(cx),
 1293            "
 1294                class Foo:
 1295                    # Hello!
 1296
 1297                    def a():⋯
 1298
 1299                    def b():
 1300                        print(2)
 1301
 1302
 1303                class Bar:
 1304                    # World!
 1305
 1306                    def a():
 1307                        print(1)
 1308
 1309                    def b():
 1310                        print(2)
 1311
 1312
 1313            "
 1314            .unindent(),
 1315        );
 1316    });
 1317}
 1318
 1319#[gpui::test]
 1320fn test_move_cursor(cx: &mut TestAppContext) {
 1321    init_test(cx, |_| {});
 1322
 1323    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1324    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1325
 1326    buffer.update(cx, |buffer, cx| {
 1327        buffer.edit(
 1328            vec![
 1329                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1330                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1331            ],
 1332            None,
 1333            cx,
 1334        );
 1335    });
 1336    _ = editor.update(cx, |editor, window, cx| {
 1337        assert_eq!(
 1338            editor.selections.display_ranges(cx),
 1339            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1340        );
 1341
 1342        editor.move_down(&MoveDown, window, cx);
 1343        assert_eq!(
 1344            editor.selections.display_ranges(cx),
 1345            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1346        );
 1347
 1348        editor.move_right(&MoveRight, window, cx);
 1349        assert_eq!(
 1350            editor.selections.display_ranges(cx),
 1351            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1352        );
 1353
 1354        editor.move_left(&MoveLeft, window, cx);
 1355        assert_eq!(
 1356            editor.selections.display_ranges(cx),
 1357            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1358        );
 1359
 1360        editor.move_up(&MoveUp, window, cx);
 1361        assert_eq!(
 1362            editor.selections.display_ranges(cx),
 1363            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1364        );
 1365
 1366        editor.move_to_end(&MoveToEnd, window, cx);
 1367        assert_eq!(
 1368            editor.selections.display_ranges(cx),
 1369            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1370        );
 1371
 1372        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1373        assert_eq!(
 1374            editor.selections.display_ranges(cx),
 1375            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1376        );
 1377
 1378        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1379            s.select_display_ranges([
 1380                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1381            ]);
 1382        });
 1383        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1384        assert_eq!(
 1385            editor.selections.display_ranges(cx),
 1386            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1387        );
 1388
 1389        editor.select_to_end(&SelectToEnd, window, cx);
 1390        assert_eq!(
 1391            editor.selections.display_ranges(cx),
 1392            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1393        );
 1394    });
 1395}
 1396
 1397#[gpui::test]
 1398fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1399    init_test(cx, |_| {});
 1400
 1401    let editor = cx.add_window(|window, cx| {
 1402        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1403        build_editor(buffer, window, cx)
 1404    });
 1405
 1406    assert_eq!('🟥'.len_utf8(), 4);
 1407    assert_eq!('α'.len_utf8(), 2);
 1408
 1409    _ = editor.update(cx, |editor, window, cx| {
 1410        editor.fold_creases(
 1411            vec![
 1412                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1413                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1414                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1415            ],
 1416            true,
 1417            window,
 1418            cx,
 1419        );
 1420        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1421
 1422        editor.move_right(&MoveRight, window, cx);
 1423        assert_eq!(
 1424            editor.selections.display_ranges(cx),
 1425            &[empty_range(0, "🟥".len())]
 1426        );
 1427        editor.move_right(&MoveRight, window, cx);
 1428        assert_eq!(
 1429            editor.selections.display_ranges(cx),
 1430            &[empty_range(0, "🟥🟧".len())]
 1431        );
 1432        editor.move_right(&MoveRight, window, cx);
 1433        assert_eq!(
 1434            editor.selections.display_ranges(cx),
 1435            &[empty_range(0, "🟥🟧⋯".len())]
 1436        );
 1437
 1438        editor.move_down(&MoveDown, window, cx);
 1439        assert_eq!(
 1440            editor.selections.display_ranges(cx),
 1441            &[empty_range(1, "ab⋯e".len())]
 1442        );
 1443        editor.move_left(&MoveLeft, window, cx);
 1444        assert_eq!(
 1445            editor.selections.display_ranges(cx),
 1446            &[empty_range(1, "ab⋯".len())]
 1447        );
 1448        editor.move_left(&MoveLeft, window, cx);
 1449        assert_eq!(
 1450            editor.selections.display_ranges(cx),
 1451            &[empty_range(1, "ab".len())]
 1452        );
 1453        editor.move_left(&MoveLeft, window, cx);
 1454        assert_eq!(
 1455            editor.selections.display_ranges(cx),
 1456            &[empty_range(1, "a".len())]
 1457        );
 1458
 1459        editor.move_down(&MoveDown, window, cx);
 1460        assert_eq!(
 1461            editor.selections.display_ranges(cx),
 1462            &[empty_range(2, "α".len())]
 1463        );
 1464        editor.move_right(&MoveRight, window, cx);
 1465        assert_eq!(
 1466            editor.selections.display_ranges(cx),
 1467            &[empty_range(2, "αβ".len())]
 1468        );
 1469        editor.move_right(&MoveRight, window, cx);
 1470        assert_eq!(
 1471            editor.selections.display_ranges(cx),
 1472            &[empty_range(2, "αβ⋯".len())]
 1473        );
 1474        editor.move_right(&MoveRight, window, cx);
 1475        assert_eq!(
 1476            editor.selections.display_ranges(cx),
 1477            &[empty_range(2, "αβ⋯ε".len())]
 1478        );
 1479
 1480        editor.move_up(&MoveUp, window, cx);
 1481        assert_eq!(
 1482            editor.selections.display_ranges(cx),
 1483            &[empty_range(1, "ab⋯e".len())]
 1484        );
 1485        editor.move_down(&MoveDown, window, cx);
 1486        assert_eq!(
 1487            editor.selections.display_ranges(cx),
 1488            &[empty_range(2, "αβ⋯ε".len())]
 1489        );
 1490        editor.move_up(&MoveUp, window, cx);
 1491        assert_eq!(
 1492            editor.selections.display_ranges(cx),
 1493            &[empty_range(1, "ab⋯e".len())]
 1494        );
 1495
 1496        editor.move_up(&MoveUp, window, cx);
 1497        assert_eq!(
 1498            editor.selections.display_ranges(cx),
 1499            &[empty_range(0, "🟥🟧".len())]
 1500        );
 1501        editor.move_left(&MoveLeft, window, cx);
 1502        assert_eq!(
 1503            editor.selections.display_ranges(cx),
 1504            &[empty_range(0, "🟥".len())]
 1505        );
 1506        editor.move_left(&MoveLeft, window, cx);
 1507        assert_eq!(
 1508            editor.selections.display_ranges(cx),
 1509            &[empty_range(0, "".len())]
 1510        );
 1511    });
 1512}
 1513
 1514#[gpui::test]
 1515fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1516    init_test(cx, |_| {});
 1517
 1518    let editor = cx.add_window(|window, cx| {
 1519        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1520        build_editor(buffer, window, cx)
 1521    });
 1522    _ = editor.update(cx, |editor, window, cx| {
 1523        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1524            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1525        });
 1526
 1527        // moving above start of document should move selection to start of document,
 1528        // but the next move down should still be at the original goal_x
 1529        editor.move_up(&MoveUp, window, cx);
 1530        assert_eq!(
 1531            editor.selections.display_ranges(cx),
 1532            &[empty_range(0, "".len())]
 1533        );
 1534
 1535        editor.move_down(&MoveDown, window, cx);
 1536        assert_eq!(
 1537            editor.selections.display_ranges(cx),
 1538            &[empty_range(1, "abcd".len())]
 1539        );
 1540
 1541        editor.move_down(&MoveDown, window, cx);
 1542        assert_eq!(
 1543            editor.selections.display_ranges(cx),
 1544            &[empty_range(2, "αβγ".len())]
 1545        );
 1546
 1547        editor.move_down(&MoveDown, window, cx);
 1548        assert_eq!(
 1549            editor.selections.display_ranges(cx),
 1550            &[empty_range(3, "abcd".len())]
 1551        );
 1552
 1553        editor.move_down(&MoveDown, window, cx);
 1554        assert_eq!(
 1555            editor.selections.display_ranges(cx),
 1556            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1557        );
 1558
 1559        // moving past end of document should not change goal_x
 1560        editor.move_down(&MoveDown, window, cx);
 1561        assert_eq!(
 1562            editor.selections.display_ranges(cx),
 1563            &[empty_range(5, "".len())]
 1564        );
 1565
 1566        editor.move_down(&MoveDown, window, cx);
 1567        assert_eq!(
 1568            editor.selections.display_ranges(cx),
 1569            &[empty_range(5, "".len())]
 1570        );
 1571
 1572        editor.move_up(&MoveUp, window, cx);
 1573        assert_eq!(
 1574            editor.selections.display_ranges(cx),
 1575            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1576        );
 1577
 1578        editor.move_up(&MoveUp, window, cx);
 1579        assert_eq!(
 1580            editor.selections.display_ranges(cx),
 1581            &[empty_range(3, "abcd".len())]
 1582        );
 1583
 1584        editor.move_up(&MoveUp, window, cx);
 1585        assert_eq!(
 1586            editor.selections.display_ranges(cx),
 1587            &[empty_range(2, "αβγ".len())]
 1588        );
 1589    });
 1590}
 1591
 1592#[gpui::test]
 1593fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1594    init_test(cx, |_| {});
 1595    let move_to_beg = MoveToBeginningOfLine {
 1596        stop_at_soft_wraps: true,
 1597        stop_at_indent: true,
 1598    };
 1599
 1600    let delete_to_beg = DeleteToBeginningOfLine {
 1601        stop_at_indent: false,
 1602    };
 1603
 1604    let move_to_end = MoveToEndOfLine {
 1605        stop_at_soft_wraps: true,
 1606    };
 1607
 1608    let editor = cx.add_window(|window, cx| {
 1609        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1610        build_editor(buffer, window, cx)
 1611    });
 1612    _ = editor.update(cx, |editor, window, cx| {
 1613        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1614            s.select_display_ranges([
 1615                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1616                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1617            ]);
 1618        });
 1619    });
 1620
 1621    _ = editor.update(cx, |editor, window, cx| {
 1622        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1623        assert_eq!(
 1624            editor.selections.display_ranges(cx),
 1625            &[
 1626                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1627                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1628            ]
 1629        );
 1630    });
 1631
 1632    _ = editor.update(cx, |editor, window, cx| {
 1633        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1634        assert_eq!(
 1635            editor.selections.display_ranges(cx),
 1636            &[
 1637                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1638                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1639            ]
 1640        );
 1641    });
 1642
 1643    _ = editor.update(cx, |editor, window, cx| {
 1644        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1645        assert_eq!(
 1646            editor.selections.display_ranges(cx),
 1647            &[
 1648                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1649                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1650            ]
 1651        );
 1652    });
 1653
 1654    _ = editor.update(cx, |editor, window, cx| {
 1655        editor.move_to_end_of_line(&move_to_end, window, cx);
 1656        assert_eq!(
 1657            editor.selections.display_ranges(cx),
 1658            &[
 1659                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1660                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1661            ]
 1662        );
 1663    });
 1664
 1665    // Moving to the end of line again is a no-op.
 1666    _ = editor.update(cx, |editor, window, cx| {
 1667        editor.move_to_end_of_line(&move_to_end, window, cx);
 1668        assert_eq!(
 1669            editor.selections.display_ranges(cx),
 1670            &[
 1671                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1672                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1673            ]
 1674        );
 1675    });
 1676
 1677    _ = editor.update(cx, |editor, window, cx| {
 1678        editor.move_left(&MoveLeft, window, cx);
 1679        editor.select_to_beginning_of_line(
 1680            &SelectToBeginningOfLine {
 1681                stop_at_soft_wraps: true,
 1682                stop_at_indent: true,
 1683            },
 1684            window,
 1685            cx,
 1686        );
 1687        assert_eq!(
 1688            editor.selections.display_ranges(cx),
 1689            &[
 1690                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1691                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1692            ]
 1693        );
 1694    });
 1695
 1696    _ = editor.update(cx, |editor, window, cx| {
 1697        editor.select_to_beginning_of_line(
 1698            &SelectToBeginningOfLine {
 1699                stop_at_soft_wraps: true,
 1700                stop_at_indent: true,
 1701            },
 1702            window,
 1703            cx,
 1704        );
 1705        assert_eq!(
 1706            editor.selections.display_ranges(cx),
 1707            &[
 1708                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1709                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1710            ]
 1711        );
 1712    });
 1713
 1714    _ = editor.update(cx, |editor, window, cx| {
 1715        editor.select_to_beginning_of_line(
 1716            &SelectToBeginningOfLine {
 1717                stop_at_soft_wraps: true,
 1718                stop_at_indent: true,
 1719            },
 1720            window,
 1721            cx,
 1722        );
 1723        assert_eq!(
 1724            editor.selections.display_ranges(cx),
 1725            &[
 1726                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1727                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1728            ]
 1729        );
 1730    });
 1731
 1732    _ = editor.update(cx, |editor, window, cx| {
 1733        editor.select_to_end_of_line(
 1734            &SelectToEndOfLine {
 1735                stop_at_soft_wraps: true,
 1736            },
 1737            window,
 1738            cx,
 1739        );
 1740        assert_eq!(
 1741            editor.selections.display_ranges(cx),
 1742            &[
 1743                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1744                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1745            ]
 1746        );
 1747    });
 1748
 1749    _ = editor.update(cx, |editor, window, cx| {
 1750        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1751        assert_eq!(editor.display_text(cx), "ab\n  de");
 1752        assert_eq!(
 1753            editor.selections.display_ranges(cx),
 1754            &[
 1755                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1756                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1757            ]
 1758        );
 1759    });
 1760
 1761    _ = editor.update(cx, |editor, window, cx| {
 1762        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1763        assert_eq!(editor.display_text(cx), "\n");
 1764        assert_eq!(
 1765            editor.selections.display_ranges(cx),
 1766            &[
 1767                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1768                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1769            ]
 1770        );
 1771    });
 1772}
 1773
 1774#[gpui::test]
 1775fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1776    init_test(cx, |_| {});
 1777    let move_to_beg = MoveToBeginningOfLine {
 1778        stop_at_soft_wraps: false,
 1779        stop_at_indent: false,
 1780    };
 1781
 1782    let move_to_end = MoveToEndOfLine {
 1783        stop_at_soft_wraps: false,
 1784    };
 1785
 1786    let editor = cx.add_window(|window, cx| {
 1787        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1788        build_editor(buffer, window, cx)
 1789    });
 1790
 1791    _ = editor.update(cx, |editor, window, cx| {
 1792        editor.set_wrap_width(Some(140.0.into()), cx);
 1793
 1794        // We expect the following lines after wrapping
 1795        // ```
 1796        // thequickbrownfox
 1797        // jumpedoverthelazydo
 1798        // gs
 1799        // ```
 1800        // The final `gs` was soft-wrapped onto a new line.
 1801        assert_eq!(
 1802            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1803            editor.display_text(cx),
 1804        );
 1805
 1806        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1807        // Start the cursor at the `k` on the first line
 1808        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1809            s.select_display_ranges([
 1810                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1811            ]);
 1812        });
 1813
 1814        // Moving to the beginning of the line should put us at the beginning of the line.
 1815        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1816        assert_eq!(
 1817            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1818            editor.selections.display_ranges(cx)
 1819        );
 1820
 1821        // Moving to the end of the line should put us at the end of the line.
 1822        editor.move_to_end_of_line(&move_to_end, window, cx);
 1823        assert_eq!(
 1824            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1825            editor.selections.display_ranges(cx)
 1826        );
 1827
 1828        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1829        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1830        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1831            s.select_display_ranges([
 1832                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1833            ]);
 1834        });
 1835
 1836        // Moving to the beginning of the line should put us at the start of the second line of
 1837        // display text, i.e., the `j`.
 1838        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1839        assert_eq!(
 1840            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1841            editor.selections.display_ranges(cx)
 1842        );
 1843
 1844        // Moving to the beginning of the line again should be a no-op.
 1845        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1846        assert_eq!(
 1847            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1848            editor.selections.display_ranges(cx)
 1849        );
 1850
 1851        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1852        // next display line.
 1853        editor.move_to_end_of_line(&move_to_end, window, cx);
 1854        assert_eq!(
 1855            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1856            editor.selections.display_ranges(cx)
 1857        );
 1858
 1859        // Moving to the end of the line again should be a no-op.
 1860        editor.move_to_end_of_line(&move_to_end, window, cx);
 1861        assert_eq!(
 1862            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1863            editor.selections.display_ranges(cx)
 1864        );
 1865    });
 1866}
 1867
 1868#[gpui::test]
 1869fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1870    init_test(cx, |_| {});
 1871
 1872    let move_to_beg = MoveToBeginningOfLine {
 1873        stop_at_soft_wraps: true,
 1874        stop_at_indent: true,
 1875    };
 1876
 1877    let select_to_beg = SelectToBeginningOfLine {
 1878        stop_at_soft_wraps: true,
 1879        stop_at_indent: true,
 1880    };
 1881
 1882    let delete_to_beg = DeleteToBeginningOfLine {
 1883        stop_at_indent: true,
 1884    };
 1885
 1886    let move_to_end = MoveToEndOfLine {
 1887        stop_at_soft_wraps: false,
 1888    };
 1889
 1890    let editor = cx.add_window(|window, cx| {
 1891        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1892        build_editor(buffer, window, cx)
 1893    });
 1894
 1895    _ = editor.update(cx, |editor, window, cx| {
 1896        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1897            s.select_display_ranges([
 1898                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1899                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1900            ]);
 1901        });
 1902
 1903        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1904        // and the second cursor at the first non-whitespace character in the line.
 1905        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1906        assert_eq!(
 1907            editor.selections.display_ranges(cx),
 1908            &[
 1909                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1910                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1911            ]
 1912        );
 1913
 1914        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1915        // and should move the second cursor to the beginning of the line.
 1916        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1917        assert_eq!(
 1918            editor.selections.display_ranges(cx),
 1919            &[
 1920                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1921                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1922            ]
 1923        );
 1924
 1925        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1926        // and should move the second cursor back to the first non-whitespace character in the line.
 1927        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1928        assert_eq!(
 1929            editor.selections.display_ranges(cx),
 1930            &[
 1931                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1932                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1933            ]
 1934        );
 1935
 1936        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1937        // and to the first non-whitespace character in the line for the second cursor.
 1938        editor.move_to_end_of_line(&move_to_end, window, cx);
 1939        editor.move_left(&MoveLeft, window, cx);
 1940        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1941        assert_eq!(
 1942            editor.selections.display_ranges(cx),
 1943            &[
 1944                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1945                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1946            ]
 1947        );
 1948
 1949        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1950        // and should select to the beginning of the line for the second cursor.
 1951        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1952        assert_eq!(
 1953            editor.selections.display_ranges(cx),
 1954            &[
 1955                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1956                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1957            ]
 1958        );
 1959
 1960        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1961        // and should delete to the first non-whitespace character in the line for the second cursor.
 1962        editor.move_to_end_of_line(&move_to_end, window, cx);
 1963        editor.move_left(&MoveLeft, window, cx);
 1964        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1965        assert_eq!(editor.text(cx), "c\n  f");
 1966    });
 1967}
 1968
 1969#[gpui::test]
 1970fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 1971    init_test(cx, |_| {});
 1972
 1973    let move_to_beg = MoveToBeginningOfLine {
 1974        stop_at_soft_wraps: true,
 1975        stop_at_indent: true,
 1976    };
 1977
 1978    let editor = cx.add_window(|window, cx| {
 1979        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 1980        build_editor(buffer, window, cx)
 1981    });
 1982
 1983    _ = editor.update(cx, |editor, window, cx| {
 1984        // test cursor between line_start and indent_start
 1985        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1986            s.select_display_ranges([
 1987                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 1988            ]);
 1989        });
 1990
 1991        // cursor should move to line_start
 1992        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1993        assert_eq!(
 1994            editor.selections.display_ranges(cx),
 1995            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1996        );
 1997
 1998        // cursor should move to indent_start
 1999        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2000        assert_eq!(
 2001            editor.selections.display_ranges(cx),
 2002            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 2003        );
 2004
 2005        // cursor should move to back to line_start
 2006        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2007        assert_eq!(
 2008            editor.selections.display_ranges(cx),
 2009            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2010        );
 2011    });
 2012}
 2013
 2014#[gpui::test]
 2015fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 2016    init_test(cx, |_| {});
 2017
 2018    let editor = cx.add_window(|window, cx| {
 2019        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 2020        build_editor(buffer, window, cx)
 2021    });
 2022    _ = editor.update(cx, |editor, window, cx| {
 2023        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2024            s.select_display_ranges([
 2025                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 2026                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 2027            ])
 2028        });
 2029        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2030        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 2031
 2032        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2033        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 2034
 2035        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2036        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2037
 2038        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2039        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2040
 2041        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2042        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 2043
 2044        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2045        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2046
 2047        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2048        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 2049
 2050        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2051        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2052
 2053        editor.move_right(&MoveRight, window, cx);
 2054        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2055        assert_selection_ranges(
 2056            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 2057            editor,
 2058            cx,
 2059        );
 2060
 2061        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2062        assert_selection_ranges(
 2063            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2064            editor,
 2065            cx,
 2066        );
 2067
 2068        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2069        assert_selection_ranges(
 2070            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2071            editor,
 2072            cx,
 2073        );
 2074    });
 2075}
 2076
 2077#[gpui::test]
 2078fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2079    init_test(cx, |_| {});
 2080
 2081    let editor = cx.add_window(|window, cx| {
 2082        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2083        build_editor(buffer, window, cx)
 2084    });
 2085
 2086    _ = editor.update(cx, |editor, window, cx| {
 2087        editor.set_wrap_width(Some(140.0.into()), cx);
 2088        assert_eq!(
 2089            editor.display_text(cx),
 2090            "use one::{\n    two::three::\n    four::five\n};"
 2091        );
 2092
 2093        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2094            s.select_display_ranges([
 2095                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2096            ]);
 2097        });
 2098
 2099        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2100        assert_eq!(
 2101            editor.selections.display_ranges(cx),
 2102            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2103        );
 2104
 2105        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2106        assert_eq!(
 2107            editor.selections.display_ranges(cx),
 2108            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2109        );
 2110
 2111        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2112        assert_eq!(
 2113            editor.selections.display_ranges(cx),
 2114            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2115        );
 2116
 2117        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2118        assert_eq!(
 2119            editor.selections.display_ranges(cx),
 2120            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2121        );
 2122
 2123        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2124        assert_eq!(
 2125            editor.selections.display_ranges(cx),
 2126            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2127        );
 2128
 2129        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2130        assert_eq!(
 2131            editor.selections.display_ranges(cx),
 2132            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2133        );
 2134    });
 2135}
 2136
 2137#[gpui::test]
 2138async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2139    init_test(cx, |_| {});
 2140    let mut cx = EditorTestContext::new(cx).await;
 2141
 2142    let line_height = cx.editor(|editor, window, _| {
 2143        editor
 2144            .style()
 2145            .unwrap()
 2146            .text
 2147            .line_height_in_pixels(window.rem_size())
 2148    });
 2149    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2150
 2151    cx.set_state(
 2152        &r#"ˇone
 2153        two
 2154
 2155        three
 2156        fourˇ
 2157        five
 2158
 2159        six"#
 2160            .unindent(),
 2161    );
 2162
 2163    cx.update_editor(|editor, window, cx| {
 2164        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2165    });
 2166    cx.assert_editor_state(
 2167        &r#"one
 2168        two
 2169        ˇ
 2170        three
 2171        four
 2172        five
 2173        ˇ
 2174        six"#
 2175            .unindent(),
 2176    );
 2177
 2178    cx.update_editor(|editor, window, cx| {
 2179        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2180    });
 2181    cx.assert_editor_state(
 2182        &r#"one
 2183        two
 2184
 2185        three
 2186        four
 2187        five
 2188        ˇ
 2189        sixˇ"#
 2190            .unindent(),
 2191    );
 2192
 2193    cx.update_editor(|editor, window, cx| {
 2194        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2195    });
 2196    cx.assert_editor_state(
 2197        &r#"one
 2198        two
 2199
 2200        three
 2201        four
 2202        five
 2203
 2204        sixˇ"#
 2205            .unindent(),
 2206    );
 2207
 2208    cx.update_editor(|editor, window, cx| {
 2209        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2210    });
 2211    cx.assert_editor_state(
 2212        &r#"one
 2213        two
 2214
 2215        three
 2216        four
 2217        five
 2218        ˇ
 2219        six"#
 2220            .unindent(),
 2221    );
 2222
 2223    cx.update_editor(|editor, window, cx| {
 2224        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2225    });
 2226    cx.assert_editor_state(
 2227        &r#"one
 2228        two
 2229        ˇ
 2230        three
 2231        four
 2232        five
 2233
 2234        six"#
 2235            .unindent(),
 2236    );
 2237
 2238    cx.update_editor(|editor, window, cx| {
 2239        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2240    });
 2241    cx.assert_editor_state(
 2242        &r#"ˇone
 2243        two
 2244
 2245        three
 2246        four
 2247        five
 2248
 2249        six"#
 2250            .unindent(),
 2251    );
 2252}
 2253
 2254#[gpui::test]
 2255async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2256    init_test(cx, |_| {});
 2257    let mut cx = EditorTestContext::new(cx).await;
 2258    let line_height = cx.editor(|editor, window, _| {
 2259        editor
 2260            .style()
 2261            .unwrap()
 2262            .text
 2263            .line_height_in_pixels(window.rem_size())
 2264    });
 2265    let window = cx.window;
 2266    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2267
 2268    cx.set_state(
 2269        r#"ˇone
 2270        two
 2271        three
 2272        four
 2273        five
 2274        six
 2275        seven
 2276        eight
 2277        nine
 2278        ten
 2279        "#,
 2280    );
 2281
 2282    cx.update_editor(|editor, window, cx| {
 2283        assert_eq!(
 2284            editor.snapshot(window, cx).scroll_position(),
 2285            gpui::Point::new(0., 0.)
 2286        );
 2287        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2288        assert_eq!(
 2289            editor.snapshot(window, cx).scroll_position(),
 2290            gpui::Point::new(0., 3.)
 2291        );
 2292        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2293        assert_eq!(
 2294            editor.snapshot(window, cx).scroll_position(),
 2295            gpui::Point::new(0., 6.)
 2296        );
 2297        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2298        assert_eq!(
 2299            editor.snapshot(window, cx).scroll_position(),
 2300            gpui::Point::new(0., 3.)
 2301        );
 2302
 2303        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2304        assert_eq!(
 2305            editor.snapshot(window, cx).scroll_position(),
 2306            gpui::Point::new(0., 1.)
 2307        );
 2308        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2309        assert_eq!(
 2310            editor.snapshot(window, cx).scroll_position(),
 2311            gpui::Point::new(0., 3.)
 2312        );
 2313    });
 2314}
 2315
 2316#[gpui::test]
 2317async fn test_autoscroll(cx: &mut TestAppContext) {
 2318    init_test(cx, |_| {});
 2319    let mut cx = EditorTestContext::new(cx).await;
 2320
 2321    let line_height = cx.update_editor(|editor, window, cx| {
 2322        editor.set_vertical_scroll_margin(2, cx);
 2323        editor
 2324            .style()
 2325            .unwrap()
 2326            .text
 2327            .line_height_in_pixels(window.rem_size())
 2328    });
 2329    let window = cx.window;
 2330    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2331
 2332    cx.set_state(
 2333        r#"ˇone
 2334            two
 2335            three
 2336            four
 2337            five
 2338            six
 2339            seven
 2340            eight
 2341            nine
 2342            ten
 2343        "#,
 2344    );
 2345    cx.update_editor(|editor, window, cx| {
 2346        assert_eq!(
 2347            editor.snapshot(window, cx).scroll_position(),
 2348            gpui::Point::new(0., 0.0)
 2349        );
 2350    });
 2351
 2352    // Add a cursor below the visible area. Since both cursors cannot fit
 2353    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2354    // allows the vertical scroll margin below that cursor.
 2355    cx.update_editor(|editor, window, cx| {
 2356        editor.change_selections(Default::default(), window, cx, |selections| {
 2357            selections.select_ranges([
 2358                Point::new(0, 0)..Point::new(0, 0),
 2359                Point::new(6, 0)..Point::new(6, 0),
 2360            ]);
 2361        })
 2362    });
 2363    cx.update_editor(|editor, window, cx| {
 2364        assert_eq!(
 2365            editor.snapshot(window, cx).scroll_position(),
 2366            gpui::Point::new(0., 3.0)
 2367        );
 2368    });
 2369
 2370    // Move down. The editor cursor scrolls down to track the newest cursor.
 2371    cx.update_editor(|editor, window, cx| {
 2372        editor.move_down(&Default::default(), window, cx);
 2373    });
 2374    cx.update_editor(|editor, window, cx| {
 2375        assert_eq!(
 2376            editor.snapshot(window, cx).scroll_position(),
 2377            gpui::Point::new(0., 4.0)
 2378        );
 2379    });
 2380
 2381    // Add a cursor above the visible area. Since both cursors fit on screen,
 2382    // the editor scrolls to show both.
 2383    cx.update_editor(|editor, window, cx| {
 2384        editor.change_selections(Default::default(), window, cx, |selections| {
 2385            selections.select_ranges([
 2386                Point::new(1, 0)..Point::new(1, 0),
 2387                Point::new(6, 0)..Point::new(6, 0),
 2388            ]);
 2389        })
 2390    });
 2391    cx.update_editor(|editor, window, cx| {
 2392        assert_eq!(
 2393            editor.snapshot(window, cx).scroll_position(),
 2394            gpui::Point::new(0., 1.0)
 2395        );
 2396    });
 2397}
 2398
 2399#[gpui::test]
 2400async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2401    init_test(cx, |_| {});
 2402    let mut cx = EditorTestContext::new(cx).await;
 2403
 2404    let line_height = cx.editor(|editor, window, _cx| {
 2405        editor
 2406            .style()
 2407            .unwrap()
 2408            .text
 2409            .line_height_in_pixels(window.rem_size())
 2410    });
 2411    let window = cx.window;
 2412    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2413    cx.set_state(
 2414        &r#"
 2415        ˇone
 2416        two
 2417        threeˇ
 2418        four
 2419        five
 2420        six
 2421        seven
 2422        eight
 2423        nine
 2424        ten
 2425        "#
 2426        .unindent(),
 2427    );
 2428
 2429    cx.update_editor(|editor, window, cx| {
 2430        editor.move_page_down(&MovePageDown::default(), window, cx)
 2431    });
 2432    cx.assert_editor_state(
 2433        &r#"
 2434        one
 2435        two
 2436        three
 2437        ˇfour
 2438        five
 2439        sixˇ
 2440        seven
 2441        eight
 2442        nine
 2443        ten
 2444        "#
 2445        .unindent(),
 2446    );
 2447
 2448    cx.update_editor(|editor, window, cx| {
 2449        editor.move_page_down(&MovePageDown::default(), window, cx)
 2450    });
 2451    cx.assert_editor_state(
 2452        &r#"
 2453        one
 2454        two
 2455        three
 2456        four
 2457        five
 2458        six
 2459        ˇseven
 2460        eight
 2461        nineˇ
 2462        ten
 2463        "#
 2464        .unindent(),
 2465    );
 2466
 2467    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2468    cx.assert_editor_state(
 2469        &r#"
 2470        one
 2471        two
 2472        three
 2473        ˇfour
 2474        five
 2475        sixˇ
 2476        seven
 2477        eight
 2478        nine
 2479        ten
 2480        "#
 2481        .unindent(),
 2482    );
 2483
 2484    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2485    cx.assert_editor_state(
 2486        &r#"
 2487        ˇone
 2488        two
 2489        threeˇ
 2490        four
 2491        five
 2492        six
 2493        seven
 2494        eight
 2495        nine
 2496        ten
 2497        "#
 2498        .unindent(),
 2499    );
 2500
 2501    // Test select collapsing
 2502    cx.update_editor(|editor, window, cx| {
 2503        editor.move_page_down(&MovePageDown::default(), window, cx);
 2504        editor.move_page_down(&MovePageDown::default(), window, cx);
 2505        editor.move_page_down(&MovePageDown::default(), window, cx);
 2506    });
 2507    cx.assert_editor_state(
 2508        &r#"
 2509        one
 2510        two
 2511        three
 2512        four
 2513        five
 2514        six
 2515        seven
 2516        eight
 2517        nine
 2518        ˇten
 2519        ˇ"#
 2520        .unindent(),
 2521    );
 2522}
 2523
 2524#[gpui::test]
 2525async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2526    init_test(cx, |_| {});
 2527    let mut cx = EditorTestContext::new(cx).await;
 2528    cx.set_state("one «two threeˇ» four");
 2529    cx.update_editor(|editor, window, cx| {
 2530        editor.delete_to_beginning_of_line(
 2531            &DeleteToBeginningOfLine {
 2532                stop_at_indent: false,
 2533            },
 2534            window,
 2535            cx,
 2536        );
 2537        assert_eq!(editor.text(cx), " four");
 2538    });
 2539}
 2540
 2541#[gpui::test]
 2542async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2543    init_test(cx, |_| {});
 2544
 2545    let mut cx = EditorTestContext::new(cx).await;
 2546
 2547    // For an empty selection, the preceding word fragment is deleted.
 2548    // For non-empty selections, only selected characters are deleted.
 2549    cx.set_state("onˇe two t«hreˇ»e four");
 2550    cx.update_editor(|editor, window, cx| {
 2551        editor.delete_to_previous_word_start(
 2552            &DeleteToPreviousWordStart {
 2553                ignore_newlines: false,
 2554                ignore_brackets: false,
 2555            },
 2556            window,
 2557            cx,
 2558        );
 2559    });
 2560    cx.assert_editor_state("ˇe two tˇe four");
 2561
 2562    cx.set_state("e tˇwo te «fˇ»our");
 2563    cx.update_editor(|editor, window, cx| {
 2564        editor.delete_to_next_word_end(
 2565            &DeleteToNextWordEnd {
 2566                ignore_newlines: false,
 2567                ignore_brackets: false,
 2568            },
 2569            window,
 2570            cx,
 2571        );
 2572    });
 2573    cx.assert_editor_state("e tˇ te ˇour");
 2574}
 2575
 2576#[gpui::test]
 2577async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2578    init_test(cx, |_| {});
 2579
 2580    let mut cx = EditorTestContext::new(cx).await;
 2581
 2582    cx.set_state("here is some text    ˇwith a space");
 2583    cx.update_editor(|editor, window, cx| {
 2584        editor.delete_to_previous_word_start(
 2585            &DeleteToPreviousWordStart {
 2586                ignore_newlines: false,
 2587                ignore_brackets: true,
 2588            },
 2589            window,
 2590            cx,
 2591        );
 2592    });
 2593    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 2594    cx.assert_editor_state("here is some textˇwith a space");
 2595
 2596    cx.set_state("here is some text    ˇwith a space");
 2597    cx.update_editor(|editor, window, cx| {
 2598        editor.delete_to_previous_word_start(
 2599            &DeleteToPreviousWordStart {
 2600                ignore_newlines: false,
 2601                ignore_brackets: false,
 2602            },
 2603            window,
 2604            cx,
 2605        );
 2606    });
 2607    cx.assert_editor_state("here is some textˇwith a space");
 2608
 2609    cx.set_state("here is some textˇ    with a space");
 2610    cx.update_editor(|editor, window, cx| {
 2611        editor.delete_to_next_word_end(
 2612            &DeleteToNextWordEnd {
 2613                ignore_newlines: false,
 2614                ignore_brackets: true,
 2615            },
 2616            window,
 2617            cx,
 2618        );
 2619    });
 2620    // Same happens in the other direction.
 2621    cx.assert_editor_state("here is some textˇwith a space");
 2622
 2623    cx.set_state("here is some textˇ    with a space");
 2624    cx.update_editor(|editor, window, cx| {
 2625        editor.delete_to_next_word_end(
 2626            &DeleteToNextWordEnd {
 2627                ignore_newlines: false,
 2628                ignore_brackets: false,
 2629            },
 2630            window,
 2631            cx,
 2632        );
 2633    });
 2634    cx.assert_editor_state("here is some textˇwith a space");
 2635
 2636    cx.set_state("here is some textˇ    with a space");
 2637    cx.update_editor(|editor, window, cx| {
 2638        editor.delete_to_next_word_end(
 2639            &DeleteToNextWordEnd {
 2640                ignore_newlines: true,
 2641                ignore_brackets: false,
 2642            },
 2643            window,
 2644            cx,
 2645        );
 2646    });
 2647    cx.assert_editor_state("here is some textˇwith a space");
 2648    cx.update_editor(|editor, window, cx| {
 2649        editor.delete_to_previous_word_start(
 2650            &DeleteToPreviousWordStart {
 2651                ignore_newlines: true,
 2652                ignore_brackets: false,
 2653            },
 2654            window,
 2655            cx,
 2656        );
 2657    });
 2658    cx.assert_editor_state("here is some ˇwith a space");
 2659    cx.update_editor(|editor, window, cx| {
 2660        editor.delete_to_previous_word_start(
 2661            &DeleteToPreviousWordStart {
 2662                ignore_newlines: true,
 2663                ignore_brackets: false,
 2664            },
 2665            window,
 2666            cx,
 2667        );
 2668    });
 2669    // Single whitespaces are removed with the word behind them.
 2670    cx.assert_editor_state("here is ˇwith a space");
 2671    cx.update_editor(|editor, window, cx| {
 2672        editor.delete_to_previous_word_start(
 2673            &DeleteToPreviousWordStart {
 2674                ignore_newlines: true,
 2675                ignore_brackets: false,
 2676            },
 2677            window,
 2678            cx,
 2679        );
 2680    });
 2681    cx.assert_editor_state("here ˇwith a space");
 2682    cx.update_editor(|editor, window, cx| {
 2683        editor.delete_to_previous_word_start(
 2684            &DeleteToPreviousWordStart {
 2685                ignore_newlines: true,
 2686                ignore_brackets: false,
 2687            },
 2688            window,
 2689            cx,
 2690        );
 2691    });
 2692    cx.assert_editor_state("ˇwith a space");
 2693    cx.update_editor(|editor, window, cx| {
 2694        editor.delete_to_previous_word_start(
 2695            &DeleteToPreviousWordStart {
 2696                ignore_newlines: true,
 2697                ignore_brackets: false,
 2698            },
 2699            window,
 2700            cx,
 2701        );
 2702    });
 2703    cx.assert_editor_state("ˇwith a space");
 2704    cx.update_editor(|editor, window, cx| {
 2705        editor.delete_to_next_word_end(
 2706            &DeleteToNextWordEnd {
 2707                ignore_newlines: true,
 2708                ignore_brackets: false,
 2709            },
 2710            window,
 2711            cx,
 2712        );
 2713    });
 2714    // Same happens in the other direction.
 2715    cx.assert_editor_state("ˇ a space");
 2716    cx.update_editor(|editor, window, cx| {
 2717        editor.delete_to_next_word_end(
 2718            &DeleteToNextWordEnd {
 2719                ignore_newlines: true,
 2720                ignore_brackets: false,
 2721            },
 2722            window,
 2723            cx,
 2724        );
 2725    });
 2726    cx.assert_editor_state("ˇ space");
 2727    cx.update_editor(|editor, window, cx| {
 2728        editor.delete_to_next_word_end(
 2729            &DeleteToNextWordEnd {
 2730                ignore_newlines: true,
 2731                ignore_brackets: false,
 2732            },
 2733            window,
 2734            cx,
 2735        );
 2736    });
 2737    cx.assert_editor_state("ˇ");
 2738    cx.update_editor(|editor, window, cx| {
 2739        editor.delete_to_next_word_end(
 2740            &DeleteToNextWordEnd {
 2741                ignore_newlines: true,
 2742                ignore_brackets: false,
 2743            },
 2744            window,
 2745            cx,
 2746        );
 2747    });
 2748    cx.assert_editor_state("ˇ");
 2749    cx.update_editor(|editor, window, cx| {
 2750        editor.delete_to_previous_word_start(
 2751            &DeleteToPreviousWordStart {
 2752                ignore_newlines: true,
 2753                ignore_brackets: false,
 2754            },
 2755            window,
 2756            cx,
 2757        );
 2758    });
 2759    cx.assert_editor_state("ˇ");
 2760}
 2761
 2762#[gpui::test]
 2763async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2764    init_test(cx, |_| {});
 2765
 2766    let language = Arc::new(
 2767        Language::new(
 2768            LanguageConfig {
 2769                brackets: BracketPairConfig {
 2770                    pairs: vec![
 2771                        BracketPair {
 2772                            start: "\"".to_string(),
 2773                            end: "\"".to_string(),
 2774                            close: true,
 2775                            surround: true,
 2776                            newline: false,
 2777                        },
 2778                        BracketPair {
 2779                            start: "(".to_string(),
 2780                            end: ")".to_string(),
 2781                            close: true,
 2782                            surround: true,
 2783                            newline: true,
 2784                        },
 2785                    ],
 2786                    ..BracketPairConfig::default()
 2787                },
 2788                ..LanguageConfig::default()
 2789            },
 2790            Some(tree_sitter_rust::LANGUAGE.into()),
 2791        )
 2792        .with_brackets_query(
 2793            r#"
 2794                ("(" @open ")" @close)
 2795                ("\"" @open "\"" @close)
 2796            "#,
 2797        )
 2798        .unwrap(),
 2799    );
 2800
 2801    let mut cx = EditorTestContext::new(cx).await;
 2802    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2803
 2804    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2805    cx.update_editor(|editor, window, cx| {
 2806        editor.delete_to_previous_word_start(
 2807            &DeleteToPreviousWordStart {
 2808                ignore_newlines: true,
 2809                ignore_brackets: false,
 2810            },
 2811            window,
 2812            cx,
 2813        );
 2814    });
 2815    // Deletion stops before brackets if asked to not ignore them.
 2816    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 2817    cx.update_editor(|editor, window, cx| {
 2818        editor.delete_to_previous_word_start(
 2819            &DeleteToPreviousWordStart {
 2820                ignore_newlines: true,
 2821                ignore_brackets: false,
 2822            },
 2823            window,
 2824            cx,
 2825        );
 2826    });
 2827    // Deletion has to remove a single bracket and then stop again.
 2828    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 2829
 2830    cx.update_editor(|editor, window, cx| {
 2831        editor.delete_to_previous_word_start(
 2832            &DeleteToPreviousWordStart {
 2833                ignore_newlines: true,
 2834                ignore_brackets: false,
 2835            },
 2836            window,
 2837            cx,
 2838        );
 2839    });
 2840    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 2841
 2842    cx.update_editor(|editor, window, cx| {
 2843        editor.delete_to_previous_word_start(
 2844            &DeleteToPreviousWordStart {
 2845                ignore_newlines: true,
 2846                ignore_brackets: false,
 2847            },
 2848            window,
 2849            cx,
 2850        );
 2851    });
 2852    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2853
 2854    cx.update_editor(|editor, window, cx| {
 2855        editor.delete_to_previous_word_start(
 2856            &DeleteToPreviousWordStart {
 2857                ignore_newlines: true,
 2858                ignore_brackets: false,
 2859            },
 2860            window,
 2861            cx,
 2862        );
 2863    });
 2864    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2865
 2866    cx.update_editor(|editor, window, cx| {
 2867        editor.delete_to_next_word_end(
 2868            &DeleteToNextWordEnd {
 2869                ignore_newlines: true,
 2870                ignore_brackets: false,
 2871            },
 2872            window,
 2873            cx,
 2874        );
 2875    });
 2876    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 2877    cx.assert_editor_state(r#"ˇ");"#);
 2878
 2879    cx.update_editor(|editor, window, cx| {
 2880        editor.delete_to_next_word_end(
 2881            &DeleteToNextWordEnd {
 2882                ignore_newlines: true,
 2883                ignore_brackets: false,
 2884            },
 2885            window,
 2886            cx,
 2887        );
 2888    });
 2889    cx.assert_editor_state(r#"ˇ"#);
 2890
 2891    cx.update_editor(|editor, window, cx| {
 2892        editor.delete_to_next_word_end(
 2893            &DeleteToNextWordEnd {
 2894                ignore_newlines: true,
 2895                ignore_brackets: false,
 2896            },
 2897            window,
 2898            cx,
 2899        );
 2900    });
 2901    cx.assert_editor_state(r#"ˇ"#);
 2902
 2903    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2904    cx.update_editor(|editor, window, cx| {
 2905        editor.delete_to_previous_word_start(
 2906            &DeleteToPreviousWordStart {
 2907                ignore_newlines: true,
 2908                ignore_brackets: true,
 2909            },
 2910            window,
 2911            cx,
 2912        );
 2913    });
 2914    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 2915}
 2916
 2917#[gpui::test]
 2918fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2919    init_test(cx, |_| {});
 2920
 2921    let editor = cx.add_window(|window, cx| {
 2922        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2923        build_editor(buffer, window, cx)
 2924    });
 2925    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2926        ignore_newlines: false,
 2927        ignore_brackets: false,
 2928    };
 2929    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2930        ignore_newlines: true,
 2931        ignore_brackets: false,
 2932    };
 2933
 2934    _ = editor.update(cx, |editor, window, cx| {
 2935        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2936            s.select_display_ranges([
 2937                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2938            ])
 2939        });
 2940        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2941        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2942        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2943        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2944        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2945        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2946        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2947        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2948        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2949        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2950        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2951        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2952    });
 2953}
 2954
 2955#[gpui::test]
 2956fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2957    init_test(cx, |_| {});
 2958
 2959    let editor = cx.add_window(|window, cx| {
 2960        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2961        build_editor(buffer, window, cx)
 2962    });
 2963    let del_to_next_word_end = DeleteToNextWordEnd {
 2964        ignore_newlines: false,
 2965        ignore_brackets: false,
 2966    };
 2967    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2968        ignore_newlines: true,
 2969        ignore_brackets: false,
 2970    };
 2971
 2972    _ = editor.update(cx, |editor, window, cx| {
 2973        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2974            s.select_display_ranges([
 2975                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2976            ])
 2977        });
 2978        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2979        assert_eq!(
 2980            editor.buffer.read(cx).read(cx).text(),
 2981            "one\n   two\nthree\n   four"
 2982        );
 2983        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2984        assert_eq!(
 2985            editor.buffer.read(cx).read(cx).text(),
 2986            "\n   two\nthree\n   four"
 2987        );
 2988        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2989        assert_eq!(
 2990            editor.buffer.read(cx).read(cx).text(),
 2991            "two\nthree\n   four"
 2992        );
 2993        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2994        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2995        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2996        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2997        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2998        assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
 2999        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3000        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3001    });
 3002}
 3003
 3004#[gpui::test]
 3005fn test_newline(cx: &mut TestAppContext) {
 3006    init_test(cx, |_| {});
 3007
 3008    let editor = cx.add_window(|window, cx| {
 3009        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 3010        build_editor(buffer, window, cx)
 3011    });
 3012
 3013    _ = editor.update(cx, |editor, window, cx| {
 3014        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3015            s.select_display_ranges([
 3016                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 3017                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 3018                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 3019            ])
 3020        });
 3021
 3022        editor.newline(&Newline, window, cx);
 3023        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 3024    });
 3025}
 3026
 3027#[gpui::test]
 3028fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 3029    init_test(cx, |_| {});
 3030
 3031    let editor = cx.add_window(|window, cx| {
 3032        let buffer = MultiBuffer::build_simple(
 3033            "
 3034                a
 3035                b(
 3036                    X
 3037                )
 3038                c(
 3039                    X
 3040                )
 3041            "
 3042            .unindent()
 3043            .as_str(),
 3044            cx,
 3045        );
 3046        let mut editor = build_editor(buffer, window, cx);
 3047        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3048            s.select_ranges([
 3049                Point::new(2, 4)..Point::new(2, 5),
 3050                Point::new(5, 4)..Point::new(5, 5),
 3051            ])
 3052        });
 3053        editor
 3054    });
 3055
 3056    _ = editor.update(cx, |editor, window, cx| {
 3057        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3058        editor.buffer.update(cx, |buffer, cx| {
 3059            buffer.edit(
 3060                [
 3061                    (Point::new(1, 2)..Point::new(3, 0), ""),
 3062                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3063                ],
 3064                None,
 3065                cx,
 3066            );
 3067            assert_eq!(
 3068                buffer.read(cx).text(),
 3069                "
 3070                    a
 3071                    b()
 3072                    c()
 3073                "
 3074                .unindent()
 3075            );
 3076        });
 3077        assert_eq!(
 3078            editor.selections.ranges(cx),
 3079            &[
 3080                Point::new(1, 2)..Point::new(1, 2),
 3081                Point::new(2, 2)..Point::new(2, 2),
 3082            ],
 3083        );
 3084
 3085        editor.newline(&Newline, window, cx);
 3086        assert_eq!(
 3087            editor.text(cx),
 3088            "
 3089                a
 3090                b(
 3091                )
 3092                c(
 3093                )
 3094            "
 3095            .unindent()
 3096        );
 3097
 3098        // The selections are moved after the inserted newlines
 3099        assert_eq!(
 3100            editor.selections.ranges(cx),
 3101            &[
 3102                Point::new(2, 0)..Point::new(2, 0),
 3103                Point::new(4, 0)..Point::new(4, 0),
 3104            ],
 3105        );
 3106    });
 3107}
 3108
 3109#[gpui::test]
 3110async fn test_newline_above(cx: &mut TestAppContext) {
 3111    init_test(cx, |settings| {
 3112        settings.defaults.tab_size = NonZeroU32::new(4)
 3113    });
 3114
 3115    let language = Arc::new(
 3116        Language::new(
 3117            LanguageConfig::default(),
 3118            Some(tree_sitter_rust::LANGUAGE.into()),
 3119        )
 3120        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3121        .unwrap(),
 3122    );
 3123
 3124    let mut cx = EditorTestContext::new(cx).await;
 3125    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3126    cx.set_state(indoc! {"
 3127        const a: ˇA = (
 3128 3129                «const_functionˇ»(ˇ),
 3130                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3131 3132        ˇ);ˇ
 3133    "});
 3134
 3135    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3136    cx.assert_editor_state(indoc! {"
 3137        ˇ
 3138        const a: A = (
 3139            ˇ
 3140            (
 3141                ˇ
 3142                ˇ
 3143                const_function(),
 3144                ˇ
 3145                ˇ
 3146                ˇ
 3147                ˇ
 3148                something_else,
 3149                ˇ
 3150            )
 3151            ˇ
 3152            ˇ
 3153        );
 3154    "});
 3155}
 3156
 3157#[gpui::test]
 3158async fn test_newline_below(cx: &mut TestAppContext) {
 3159    init_test(cx, |settings| {
 3160        settings.defaults.tab_size = NonZeroU32::new(4)
 3161    });
 3162
 3163    let language = Arc::new(
 3164        Language::new(
 3165            LanguageConfig::default(),
 3166            Some(tree_sitter_rust::LANGUAGE.into()),
 3167        )
 3168        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3169        .unwrap(),
 3170    );
 3171
 3172    let mut cx = EditorTestContext::new(cx).await;
 3173    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3174    cx.set_state(indoc! {"
 3175        const a: ˇA = (
 3176 3177                «const_functionˇ»(ˇ),
 3178                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3179 3180        ˇ);ˇ
 3181    "});
 3182
 3183    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3184    cx.assert_editor_state(indoc! {"
 3185        const a: A = (
 3186            ˇ
 3187            (
 3188                ˇ
 3189                const_function(),
 3190                ˇ
 3191                ˇ
 3192                something_else,
 3193                ˇ
 3194                ˇ
 3195                ˇ
 3196                ˇ
 3197            )
 3198            ˇ
 3199        );
 3200        ˇ
 3201        ˇ
 3202    "});
 3203}
 3204
 3205#[gpui::test]
 3206async fn test_newline_comments(cx: &mut TestAppContext) {
 3207    init_test(cx, |settings| {
 3208        settings.defaults.tab_size = NonZeroU32::new(4)
 3209    });
 3210
 3211    let language = Arc::new(Language::new(
 3212        LanguageConfig {
 3213            line_comments: vec!["// ".into()],
 3214            ..LanguageConfig::default()
 3215        },
 3216        None,
 3217    ));
 3218    {
 3219        let mut cx = EditorTestContext::new(cx).await;
 3220        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3221        cx.set_state(indoc! {"
 3222        // Fooˇ
 3223    "});
 3224
 3225        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3226        cx.assert_editor_state(indoc! {"
 3227        // Foo
 3228        // ˇ
 3229    "});
 3230        // Ensure that we add comment prefix when existing line contains space
 3231        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3232        cx.assert_editor_state(
 3233            indoc! {"
 3234        // Foo
 3235        //s
 3236        // ˇ
 3237    "}
 3238            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3239            .as_str(),
 3240        );
 3241        // Ensure that we add comment prefix when existing line does not contain space
 3242        cx.set_state(indoc! {"
 3243        // Foo
 3244        //ˇ
 3245    "});
 3246        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3247        cx.assert_editor_state(indoc! {"
 3248        // Foo
 3249        //
 3250        // ˇ
 3251    "});
 3252        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3253        cx.set_state(indoc! {"
 3254        ˇ// Foo
 3255    "});
 3256        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3257        cx.assert_editor_state(indoc! {"
 3258
 3259        ˇ// Foo
 3260    "});
 3261    }
 3262    // Ensure that comment continuations can be disabled.
 3263    update_test_language_settings(cx, |settings| {
 3264        settings.defaults.extend_comment_on_newline = Some(false);
 3265    });
 3266    let mut cx = EditorTestContext::new(cx).await;
 3267    cx.set_state(indoc! {"
 3268        // Fooˇ
 3269    "});
 3270    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3271    cx.assert_editor_state(indoc! {"
 3272        // Foo
 3273        ˇ
 3274    "});
 3275}
 3276
 3277#[gpui::test]
 3278async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3279    init_test(cx, |settings| {
 3280        settings.defaults.tab_size = NonZeroU32::new(4)
 3281    });
 3282
 3283    let language = Arc::new(Language::new(
 3284        LanguageConfig {
 3285            line_comments: vec!["// ".into(), "/// ".into()],
 3286            ..LanguageConfig::default()
 3287        },
 3288        None,
 3289    ));
 3290    {
 3291        let mut cx = EditorTestContext::new(cx).await;
 3292        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3293        cx.set_state(indoc! {"
 3294        //ˇ
 3295    "});
 3296        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3297        cx.assert_editor_state(indoc! {"
 3298        //
 3299        // ˇ
 3300    "});
 3301
 3302        cx.set_state(indoc! {"
 3303        ///ˇ
 3304    "});
 3305        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3306        cx.assert_editor_state(indoc! {"
 3307        ///
 3308        /// ˇ
 3309    "});
 3310    }
 3311}
 3312
 3313#[gpui::test]
 3314async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3315    init_test(cx, |settings| {
 3316        settings.defaults.tab_size = NonZeroU32::new(4)
 3317    });
 3318
 3319    let language = Arc::new(
 3320        Language::new(
 3321            LanguageConfig {
 3322                documentation_comment: Some(language::BlockCommentConfig {
 3323                    start: "/**".into(),
 3324                    end: "*/".into(),
 3325                    prefix: "* ".into(),
 3326                    tab_size: 1,
 3327                }),
 3328
 3329                ..LanguageConfig::default()
 3330            },
 3331            Some(tree_sitter_rust::LANGUAGE.into()),
 3332        )
 3333        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3334        .unwrap(),
 3335    );
 3336
 3337    {
 3338        let mut cx = EditorTestContext::new(cx).await;
 3339        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3340        cx.set_state(indoc! {"
 3341        /**ˇ
 3342    "});
 3343
 3344        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3345        cx.assert_editor_state(indoc! {"
 3346        /**
 3347         * ˇ
 3348    "});
 3349        // Ensure that if cursor is before the comment start,
 3350        // we do not actually insert a comment prefix.
 3351        cx.set_state(indoc! {"
 3352        ˇ/**
 3353    "});
 3354        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3355        cx.assert_editor_state(indoc! {"
 3356
 3357        ˇ/**
 3358    "});
 3359        // Ensure that if cursor is between it doesn't add comment prefix.
 3360        cx.set_state(indoc! {"
 3361        /*ˇ*
 3362    "});
 3363        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3364        cx.assert_editor_state(indoc! {"
 3365        /*
 3366        ˇ*
 3367    "});
 3368        // Ensure that if suffix exists on same line after cursor it adds new line.
 3369        cx.set_state(indoc! {"
 3370        /**ˇ*/
 3371    "});
 3372        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3373        cx.assert_editor_state(indoc! {"
 3374        /**
 3375         * ˇ
 3376         */
 3377    "});
 3378        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3379        cx.set_state(indoc! {"
 3380        /**ˇ */
 3381    "});
 3382        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3383        cx.assert_editor_state(indoc! {"
 3384        /**
 3385         * ˇ
 3386         */
 3387    "});
 3388        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3389        cx.set_state(indoc! {"
 3390        /** ˇ*/
 3391    "});
 3392        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3393        cx.assert_editor_state(
 3394            indoc! {"
 3395        /**s
 3396         * ˇ
 3397         */
 3398    "}
 3399            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3400            .as_str(),
 3401        );
 3402        // Ensure that delimiter space is preserved when newline on already
 3403        // spaced delimiter.
 3404        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3405        cx.assert_editor_state(
 3406            indoc! {"
 3407        /**s
 3408         *s
 3409         * ˇ
 3410         */
 3411    "}
 3412            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3413            .as_str(),
 3414        );
 3415        // Ensure that delimiter space is preserved when space is not
 3416        // on existing delimiter.
 3417        cx.set_state(indoc! {"
 3418        /**
 3419 3420         */
 3421    "});
 3422        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3423        cx.assert_editor_state(indoc! {"
 3424        /**
 3425         *
 3426         * ˇ
 3427         */
 3428    "});
 3429        // Ensure that if suffix exists on same line after cursor it
 3430        // doesn't add extra new line if prefix is not on same line.
 3431        cx.set_state(indoc! {"
 3432        /**
 3433        ˇ*/
 3434    "});
 3435        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3436        cx.assert_editor_state(indoc! {"
 3437        /**
 3438
 3439        ˇ*/
 3440    "});
 3441        // Ensure that it detects suffix after existing prefix.
 3442        cx.set_state(indoc! {"
 3443        /**ˇ/
 3444    "});
 3445        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3446        cx.assert_editor_state(indoc! {"
 3447        /**
 3448        ˇ/
 3449    "});
 3450        // Ensure that if suffix exists on same line before
 3451        // cursor it does not add comment prefix.
 3452        cx.set_state(indoc! {"
 3453        /** */ˇ
 3454    "});
 3455        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3456        cx.assert_editor_state(indoc! {"
 3457        /** */
 3458        ˇ
 3459    "});
 3460        // Ensure that if suffix exists on same line before
 3461        // cursor it does not add comment prefix.
 3462        cx.set_state(indoc! {"
 3463        /**
 3464         *
 3465         */ˇ
 3466    "});
 3467        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3468        cx.assert_editor_state(indoc! {"
 3469        /**
 3470         *
 3471         */
 3472         ˇ
 3473    "});
 3474
 3475        // Ensure that inline comment followed by code
 3476        // doesn't add comment prefix on newline
 3477        cx.set_state(indoc! {"
 3478        /** */ textˇ
 3479    "});
 3480        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3481        cx.assert_editor_state(indoc! {"
 3482        /** */ text
 3483        ˇ
 3484    "});
 3485
 3486        // Ensure that text after comment end tag
 3487        // doesn't add comment prefix on newline
 3488        cx.set_state(indoc! {"
 3489        /**
 3490         *
 3491         */ˇtext
 3492    "});
 3493        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3494        cx.assert_editor_state(indoc! {"
 3495        /**
 3496         *
 3497         */
 3498         ˇtext
 3499    "});
 3500
 3501        // Ensure if not comment block it doesn't
 3502        // add comment prefix on newline
 3503        cx.set_state(indoc! {"
 3504        * textˇ
 3505    "});
 3506        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3507        cx.assert_editor_state(indoc! {"
 3508        * text
 3509        ˇ
 3510    "});
 3511    }
 3512    // Ensure that comment continuations can be disabled.
 3513    update_test_language_settings(cx, |settings| {
 3514        settings.defaults.extend_comment_on_newline = Some(false);
 3515    });
 3516    let mut cx = EditorTestContext::new(cx).await;
 3517    cx.set_state(indoc! {"
 3518        /**ˇ
 3519    "});
 3520    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3521    cx.assert_editor_state(indoc! {"
 3522        /**
 3523        ˇ
 3524    "});
 3525}
 3526
 3527#[gpui::test]
 3528async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3529    init_test(cx, |settings| {
 3530        settings.defaults.tab_size = NonZeroU32::new(4)
 3531    });
 3532
 3533    let lua_language = Arc::new(Language::new(
 3534        LanguageConfig {
 3535            line_comments: vec!["--".into()],
 3536            block_comment: Some(language::BlockCommentConfig {
 3537                start: "--[[".into(),
 3538                prefix: "".into(),
 3539                end: "]]".into(),
 3540                tab_size: 0,
 3541            }),
 3542            ..LanguageConfig::default()
 3543        },
 3544        None,
 3545    ));
 3546
 3547    let mut cx = EditorTestContext::new(cx).await;
 3548    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3549
 3550    // Line with line comment should extend
 3551    cx.set_state(indoc! {"
 3552        --ˇ
 3553    "});
 3554    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3555    cx.assert_editor_state(indoc! {"
 3556        --
 3557        --ˇ
 3558    "});
 3559
 3560    // Line with block comment that matches line comment should not extend
 3561    cx.set_state(indoc! {"
 3562        --[[ˇ
 3563    "});
 3564    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3565    cx.assert_editor_state(indoc! {"
 3566        --[[
 3567        ˇ
 3568    "});
 3569}
 3570
 3571#[gpui::test]
 3572fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3573    init_test(cx, |_| {});
 3574
 3575    let editor = cx.add_window(|window, cx| {
 3576        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3577        let mut editor = build_editor(buffer, window, cx);
 3578        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3579            s.select_ranges([3..4, 11..12, 19..20])
 3580        });
 3581        editor
 3582    });
 3583
 3584    _ = editor.update(cx, |editor, window, cx| {
 3585        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3586        editor.buffer.update(cx, |buffer, cx| {
 3587            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3588            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3589        });
 3590        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3591
 3592        editor.insert("Z", window, cx);
 3593        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3594
 3595        // The selections are moved after the inserted characters
 3596        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3597    });
 3598}
 3599
 3600#[gpui::test]
 3601async fn test_tab(cx: &mut TestAppContext) {
 3602    init_test(cx, |settings| {
 3603        settings.defaults.tab_size = NonZeroU32::new(3)
 3604    });
 3605
 3606    let mut cx = EditorTestContext::new(cx).await;
 3607    cx.set_state(indoc! {"
 3608        ˇabˇc
 3609        ˇ🏀ˇ🏀ˇefg
 3610 3611    "});
 3612    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3613    cx.assert_editor_state(indoc! {"
 3614           ˇab ˇc
 3615           ˇ🏀  ˇ🏀  ˇefg
 3616        d  ˇ
 3617    "});
 3618
 3619    cx.set_state(indoc! {"
 3620        a
 3621        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3622    "});
 3623    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3624    cx.assert_editor_state(indoc! {"
 3625        a
 3626           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3627    "});
 3628}
 3629
 3630#[gpui::test]
 3631async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3632    init_test(cx, |_| {});
 3633
 3634    let mut cx = EditorTestContext::new(cx).await;
 3635    let language = Arc::new(
 3636        Language::new(
 3637            LanguageConfig::default(),
 3638            Some(tree_sitter_rust::LANGUAGE.into()),
 3639        )
 3640        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3641        .unwrap(),
 3642    );
 3643    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3644
 3645    // test when all cursors are not at suggested indent
 3646    // then simply move to their suggested indent location
 3647    cx.set_state(indoc! {"
 3648        const a: B = (
 3649            c(
 3650        ˇ
 3651        ˇ    )
 3652        );
 3653    "});
 3654    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3655    cx.assert_editor_state(indoc! {"
 3656        const a: B = (
 3657            c(
 3658                ˇ
 3659            ˇ)
 3660        );
 3661    "});
 3662
 3663    // test cursor already at suggested indent not moving when
 3664    // other cursors are yet to reach their suggested indents
 3665    cx.set_state(indoc! {"
 3666        ˇ
 3667        const a: B = (
 3668            c(
 3669                d(
 3670        ˇ
 3671                )
 3672        ˇ
 3673        ˇ    )
 3674        );
 3675    "});
 3676    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3677    cx.assert_editor_state(indoc! {"
 3678        ˇ
 3679        const a: B = (
 3680            c(
 3681                d(
 3682                    ˇ
 3683                )
 3684                ˇ
 3685            ˇ)
 3686        );
 3687    "});
 3688    // test when all cursors are at suggested indent then tab is inserted
 3689    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3690    cx.assert_editor_state(indoc! {"
 3691            ˇ
 3692        const a: B = (
 3693            c(
 3694                d(
 3695                        ˇ
 3696                )
 3697                    ˇ
 3698                ˇ)
 3699        );
 3700    "});
 3701
 3702    // test when current indent is less than suggested indent,
 3703    // we adjust line to match suggested indent and move cursor to it
 3704    //
 3705    // when no other cursor is at word boundary, all of them should move
 3706    cx.set_state(indoc! {"
 3707        const a: B = (
 3708            c(
 3709                d(
 3710        ˇ
 3711        ˇ   )
 3712        ˇ   )
 3713        );
 3714    "});
 3715    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3716    cx.assert_editor_state(indoc! {"
 3717        const a: B = (
 3718            c(
 3719                d(
 3720                    ˇ
 3721                ˇ)
 3722            ˇ)
 3723        );
 3724    "});
 3725
 3726    // test when current indent is less than suggested indent,
 3727    // we adjust line to match suggested indent and move cursor to it
 3728    //
 3729    // when some other cursor is at word boundary, it should not move
 3730    cx.set_state(indoc! {"
 3731        const a: B = (
 3732            c(
 3733                d(
 3734        ˇ
 3735        ˇ   )
 3736           ˇ)
 3737        );
 3738    "});
 3739    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3740    cx.assert_editor_state(indoc! {"
 3741        const a: B = (
 3742            c(
 3743                d(
 3744                    ˇ
 3745                ˇ)
 3746            ˇ)
 3747        );
 3748    "});
 3749
 3750    // test when current indent is more than suggested indent,
 3751    // we just move cursor to current indent instead of suggested indent
 3752    //
 3753    // when no other cursor is at word boundary, all of them should move
 3754    cx.set_state(indoc! {"
 3755        const a: B = (
 3756            c(
 3757                d(
 3758        ˇ
 3759        ˇ                )
 3760        ˇ   )
 3761        );
 3762    "});
 3763    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3764    cx.assert_editor_state(indoc! {"
 3765        const a: B = (
 3766            c(
 3767                d(
 3768                    ˇ
 3769                        ˇ)
 3770            ˇ)
 3771        );
 3772    "});
 3773    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3774    cx.assert_editor_state(indoc! {"
 3775        const a: B = (
 3776            c(
 3777                d(
 3778                        ˇ
 3779                            ˇ)
 3780                ˇ)
 3781        );
 3782    "});
 3783
 3784    // test when current indent is more than suggested indent,
 3785    // we just move cursor to current indent instead of suggested indent
 3786    //
 3787    // when some other cursor is at word boundary, it doesn't move
 3788    cx.set_state(indoc! {"
 3789        const a: B = (
 3790            c(
 3791                d(
 3792        ˇ
 3793        ˇ                )
 3794            ˇ)
 3795        );
 3796    "});
 3797    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3798    cx.assert_editor_state(indoc! {"
 3799        const a: B = (
 3800            c(
 3801                d(
 3802                    ˇ
 3803                        ˇ)
 3804            ˇ)
 3805        );
 3806    "});
 3807
 3808    // handle auto-indent when there are multiple cursors on the same line
 3809    cx.set_state(indoc! {"
 3810        const a: B = (
 3811            c(
 3812        ˇ    ˇ
 3813        ˇ    )
 3814        );
 3815    "});
 3816    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3817    cx.assert_editor_state(indoc! {"
 3818        const a: B = (
 3819            c(
 3820                ˇ
 3821            ˇ)
 3822        );
 3823    "});
 3824}
 3825
 3826#[gpui::test]
 3827async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3828    init_test(cx, |settings| {
 3829        settings.defaults.tab_size = NonZeroU32::new(3)
 3830    });
 3831
 3832    let mut cx = EditorTestContext::new(cx).await;
 3833    cx.set_state(indoc! {"
 3834         ˇ
 3835        \t ˇ
 3836        \t  ˇ
 3837        \t   ˇ
 3838         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3839    "});
 3840
 3841    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3842    cx.assert_editor_state(indoc! {"
 3843           ˇ
 3844        \t   ˇ
 3845        \t   ˇ
 3846        \t      ˇ
 3847         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3848    "});
 3849}
 3850
 3851#[gpui::test]
 3852async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3853    init_test(cx, |settings| {
 3854        settings.defaults.tab_size = NonZeroU32::new(4)
 3855    });
 3856
 3857    let language = Arc::new(
 3858        Language::new(
 3859            LanguageConfig::default(),
 3860            Some(tree_sitter_rust::LANGUAGE.into()),
 3861        )
 3862        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3863        .unwrap(),
 3864    );
 3865
 3866    let mut cx = EditorTestContext::new(cx).await;
 3867    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3868    cx.set_state(indoc! {"
 3869        fn a() {
 3870            if b {
 3871        \t ˇc
 3872            }
 3873        }
 3874    "});
 3875
 3876    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3877    cx.assert_editor_state(indoc! {"
 3878        fn a() {
 3879            if b {
 3880                ˇc
 3881            }
 3882        }
 3883    "});
 3884}
 3885
 3886#[gpui::test]
 3887async fn test_indent_outdent(cx: &mut TestAppContext) {
 3888    init_test(cx, |settings| {
 3889        settings.defaults.tab_size = NonZeroU32::new(4);
 3890    });
 3891
 3892    let mut cx = EditorTestContext::new(cx).await;
 3893
 3894    cx.set_state(indoc! {"
 3895          «oneˇ» «twoˇ»
 3896        three
 3897         four
 3898    "});
 3899    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3900    cx.assert_editor_state(indoc! {"
 3901            «oneˇ» «twoˇ»
 3902        three
 3903         four
 3904    "});
 3905
 3906    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3907    cx.assert_editor_state(indoc! {"
 3908        «oneˇ» «twoˇ»
 3909        three
 3910         four
 3911    "});
 3912
 3913    // select across line ending
 3914    cx.set_state(indoc! {"
 3915        one two
 3916        t«hree
 3917        ˇ» four
 3918    "});
 3919    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3920    cx.assert_editor_state(indoc! {"
 3921        one two
 3922            t«hree
 3923        ˇ» four
 3924    "});
 3925
 3926    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3927    cx.assert_editor_state(indoc! {"
 3928        one two
 3929        t«hree
 3930        ˇ» four
 3931    "});
 3932
 3933    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3934    cx.set_state(indoc! {"
 3935        one two
 3936        ˇthree
 3937            four
 3938    "});
 3939    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3940    cx.assert_editor_state(indoc! {"
 3941        one two
 3942            ˇthree
 3943            four
 3944    "});
 3945
 3946    cx.set_state(indoc! {"
 3947        one two
 3948        ˇ    three
 3949            four
 3950    "});
 3951    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3952    cx.assert_editor_state(indoc! {"
 3953        one two
 3954        ˇthree
 3955            four
 3956    "});
 3957}
 3958
 3959#[gpui::test]
 3960async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3961    // This is a regression test for issue #33761
 3962    init_test(cx, |_| {});
 3963
 3964    let mut cx = EditorTestContext::new(cx).await;
 3965    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3966    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3967
 3968    cx.set_state(
 3969        r#"ˇ#     ingress:
 3970ˇ#         api:
 3971ˇ#             enabled: false
 3972ˇ#             pathType: Prefix
 3973ˇ#           console:
 3974ˇ#               enabled: false
 3975ˇ#               pathType: Prefix
 3976"#,
 3977    );
 3978
 3979    // Press tab to indent all lines
 3980    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3981
 3982    cx.assert_editor_state(
 3983        r#"    ˇ#     ingress:
 3984    ˇ#         api:
 3985    ˇ#             enabled: false
 3986    ˇ#             pathType: Prefix
 3987    ˇ#           console:
 3988    ˇ#               enabled: false
 3989    ˇ#               pathType: Prefix
 3990"#,
 3991    );
 3992}
 3993
 3994#[gpui::test]
 3995async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3996    // This is a test to make sure our fix for issue #33761 didn't break anything
 3997    init_test(cx, |_| {});
 3998
 3999    let mut cx = EditorTestContext::new(cx).await;
 4000    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4001    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4002
 4003    cx.set_state(
 4004        r#"ˇingress:
 4005ˇ  api:
 4006ˇ    enabled: false
 4007ˇ    pathType: Prefix
 4008"#,
 4009    );
 4010
 4011    // Press tab to indent all lines
 4012    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4013
 4014    cx.assert_editor_state(
 4015        r#"ˇingress:
 4016    ˇapi:
 4017        ˇenabled: false
 4018        ˇpathType: Prefix
 4019"#,
 4020    );
 4021}
 4022
 4023#[gpui::test]
 4024async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 4025    init_test(cx, |settings| {
 4026        settings.defaults.hard_tabs = Some(true);
 4027    });
 4028
 4029    let mut cx = EditorTestContext::new(cx).await;
 4030
 4031    // select two ranges on one line
 4032    cx.set_state(indoc! {"
 4033        «oneˇ» «twoˇ»
 4034        three
 4035        four
 4036    "});
 4037    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4038    cx.assert_editor_state(indoc! {"
 4039        \t«oneˇ» «twoˇ»
 4040        three
 4041        four
 4042    "});
 4043    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4044    cx.assert_editor_state(indoc! {"
 4045        \t\t«oneˇ» «twoˇ»
 4046        three
 4047        four
 4048    "});
 4049    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4050    cx.assert_editor_state(indoc! {"
 4051        \t«oneˇ» «twoˇ»
 4052        three
 4053        four
 4054    "});
 4055    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4056    cx.assert_editor_state(indoc! {"
 4057        «oneˇ» «twoˇ»
 4058        three
 4059        four
 4060    "});
 4061
 4062    // select across a line ending
 4063    cx.set_state(indoc! {"
 4064        one two
 4065        t«hree
 4066        ˇ»four
 4067    "});
 4068    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4069    cx.assert_editor_state(indoc! {"
 4070        one two
 4071        \tt«hree
 4072        ˇ»four
 4073    "});
 4074    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4075    cx.assert_editor_state(indoc! {"
 4076        one two
 4077        \t\tt«hree
 4078        ˇ»four
 4079    "});
 4080    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4081    cx.assert_editor_state(indoc! {"
 4082        one two
 4083        \tt«hree
 4084        ˇ»four
 4085    "});
 4086    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4087    cx.assert_editor_state(indoc! {"
 4088        one two
 4089        t«hree
 4090        ˇ»four
 4091    "});
 4092
 4093    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4094    cx.set_state(indoc! {"
 4095        one two
 4096        ˇthree
 4097        four
 4098    "});
 4099    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4100    cx.assert_editor_state(indoc! {"
 4101        one two
 4102        ˇthree
 4103        four
 4104    "});
 4105    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4106    cx.assert_editor_state(indoc! {"
 4107        one two
 4108        \tˇthree
 4109        four
 4110    "});
 4111    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4112    cx.assert_editor_state(indoc! {"
 4113        one two
 4114        ˇthree
 4115        four
 4116    "});
 4117}
 4118
 4119#[gpui::test]
 4120fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4121    init_test(cx, |settings| {
 4122        settings.languages.0.extend([
 4123            (
 4124                "TOML".into(),
 4125                LanguageSettingsContent {
 4126                    tab_size: NonZeroU32::new(2),
 4127                    ..Default::default()
 4128                },
 4129            ),
 4130            (
 4131                "Rust".into(),
 4132                LanguageSettingsContent {
 4133                    tab_size: NonZeroU32::new(4),
 4134                    ..Default::default()
 4135                },
 4136            ),
 4137        ]);
 4138    });
 4139
 4140    let toml_language = Arc::new(Language::new(
 4141        LanguageConfig {
 4142            name: "TOML".into(),
 4143            ..Default::default()
 4144        },
 4145        None,
 4146    ));
 4147    let rust_language = Arc::new(Language::new(
 4148        LanguageConfig {
 4149            name: "Rust".into(),
 4150            ..Default::default()
 4151        },
 4152        None,
 4153    ));
 4154
 4155    let toml_buffer =
 4156        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4157    let rust_buffer =
 4158        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4159    let multibuffer = cx.new(|cx| {
 4160        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4161        multibuffer.push_excerpts(
 4162            toml_buffer.clone(),
 4163            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4164            cx,
 4165        );
 4166        multibuffer.push_excerpts(
 4167            rust_buffer.clone(),
 4168            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4169            cx,
 4170        );
 4171        multibuffer
 4172    });
 4173
 4174    cx.add_window(|window, cx| {
 4175        let mut editor = build_editor(multibuffer, window, cx);
 4176
 4177        assert_eq!(
 4178            editor.text(cx),
 4179            indoc! {"
 4180                a = 1
 4181                b = 2
 4182
 4183                const c: usize = 3;
 4184            "}
 4185        );
 4186
 4187        select_ranges(
 4188            &mut editor,
 4189            indoc! {"
 4190                «aˇ» = 1
 4191                b = 2
 4192
 4193                «const c:ˇ» usize = 3;
 4194            "},
 4195            window,
 4196            cx,
 4197        );
 4198
 4199        editor.tab(&Tab, window, cx);
 4200        assert_text_with_selections(
 4201            &mut editor,
 4202            indoc! {"
 4203                  «aˇ» = 1
 4204                b = 2
 4205
 4206                    «const c:ˇ» usize = 3;
 4207            "},
 4208            cx,
 4209        );
 4210        editor.backtab(&Backtab, window, cx);
 4211        assert_text_with_selections(
 4212            &mut editor,
 4213            indoc! {"
 4214                «aˇ» = 1
 4215                b = 2
 4216
 4217                «const c:ˇ» usize = 3;
 4218            "},
 4219            cx,
 4220        );
 4221
 4222        editor
 4223    });
 4224}
 4225
 4226#[gpui::test]
 4227async fn test_backspace(cx: &mut TestAppContext) {
 4228    init_test(cx, |_| {});
 4229
 4230    let mut cx = EditorTestContext::new(cx).await;
 4231
 4232    // Basic backspace
 4233    cx.set_state(indoc! {"
 4234        onˇe two three
 4235        fou«rˇ» five six
 4236        seven «ˇeight nine
 4237        »ten
 4238    "});
 4239    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4240    cx.assert_editor_state(indoc! {"
 4241        oˇe two three
 4242        fouˇ five six
 4243        seven ˇten
 4244    "});
 4245
 4246    // Test backspace inside and around indents
 4247    cx.set_state(indoc! {"
 4248        zero
 4249            ˇone
 4250                ˇtwo
 4251            ˇ ˇ ˇ  three
 4252        ˇ  ˇ  four
 4253    "});
 4254    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4255    cx.assert_editor_state(indoc! {"
 4256        zero
 4257        ˇone
 4258            ˇtwo
 4259        ˇ  threeˇ  four
 4260    "});
 4261}
 4262
 4263#[gpui::test]
 4264async fn test_delete(cx: &mut TestAppContext) {
 4265    init_test(cx, |_| {});
 4266
 4267    let mut cx = EditorTestContext::new(cx).await;
 4268    cx.set_state(indoc! {"
 4269        onˇe two three
 4270        fou«rˇ» five six
 4271        seven «ˇeight nine
 4272        »ten
 4273    "});
 4274    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4275    cx.assert_editor_state(indoc! {"
 4276        onˇ two three
 4277        fouˇ five six
 4278        seven ˇten
 4279    "});
 4280}
 4281
 4282#[gpui::test]
 4283fn test_delete_line(cx: &mut TestAppContext) {
 4284    init_test(cx, |_| {});
 4285
 4286    let editor = cx.add_window(|window, cx| {
 4287        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4288        build_editor(buffer, window, cx)
 4289    });
 4290    _ = editor.update(cx, |editor, window, cx| {
 4291        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4292            s.select_display_ranges([
 4293                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4294                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4295                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4296            ])
 4297        });
 4298        editor.delete_line(&DeleteLine, window, cx);
 4299        assert_eq!(editor.display_text(cx), "ghi");
 4300        assert_eq!(
 4301            editor.selections.display_ranges(cx),
 4302            vec![
 4303                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4304                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 4305            ]
 4306        );
 4307    });
 4308
 4309    let editor = cx.add_window(|window, cx| {
 4310        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4311        build_editor(buffer, window, cx)
 4312    });
 4313    _ = editor.update(cx, |editor, window, cx| {
 4314        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4315            s.select_display_ranges([
 4316                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4317            ])
 4318        });
 4319        editor.delete_line(&DeleteLine, window, cx);
 4320        assert_eq!(editor.display_text(cx), "ghi\n");
 4321        assert_eq!(
 4322            editor.selections.display_ranges(cx),
 4323            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4324        );
 4325    });
 4326}
 4327
 4328#[gpui::test]
 4329fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4330    init_test(cx, |_| {});
 4331
 4332    cx.add_window(|window, cx| {
 4333        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4334        let mut editor = build_editor(buffer.clone(), window, cx);
 4335        let buffer = buffer.read(cx).as_singleton().unwrap();
 4336
 4337        assert_eq!(
 4338            editor.selections.ranges::<Point>(cx),
 4339            &[Point::new(0, 0)..Point::new(0, 0)]
 4340        );
 4341
 4342        // When on single line, replace newline at end by space
 4343        editor.join_lines(&JoinLines, window, cx);
 4344        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4345        assert_eq!(
 4346            editor.selections.ranges::<Point>(cx),
 4347            &[Point::new(0, 3)..Point::new(0, 3)]
 4348        );
 4349
 4350        // When multiple lines are selected, remove newlines that are spanned by the selection
 4351        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4352            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4353        });
 4354        editor.join_lines(&JoinLines, window, cx);
 4355        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4356        assert_eq!(
 4357            editor.selections.ranges::<Point>(cx),
 4358            &[Point::new(0, 11)..Point::new(0, 11)]
 4359        );
 4360
 4361        // Undo should be transactional
 4362        editor.undo(&Undo, window, cx);
 4363        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4364        assert_eq!(
 4365            editor.selections.ranges::<Point>(cx),
 4366            &[Point::new(0, 5)..Point::new(2, 2)]
 4367        );
 4368
 4369        // When joining an empty line don't insert a space
 4370        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4371            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4372        });
 4373        editor.join_lines(&JoinLines, window, cx);
 4374        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4375        assert_eq!(
 4376            editor.selections.ranges::<Point>(cx),
 4377            [Point::new(2, 3)..Point::new(2, 3)]
 4378        );
 4379
 4380        // We can remove trailing newlines
 4381        editor.join_lines(&JoinLines, window, cx);
 4382        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4383        assert_eq!(
 4384            editor.selections.ranges::<Point>(cx),
 4385            [Point::new(2, 3)..Point::new(2, 3)]
 4386        );
 4387
 4388        // We don't blow up on the last line
 4389        editor.join_lines(&JoinLines, window, cx);
 4390        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4391        assert_eq!(
 4392            editor.selections.ranges::<Point>(cx),
 4393            [Point::new(2, 3)..Point::new(2, 3)]
 4394        );
 4395
 4396        // reset to test indentation
 4397        editor.buffer.update(cx, |buffer, cx| {
 4398            buffer.edit(
 4399                [
 4400                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4401                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4402                ],
 4403                None,
 4404                cx,
 4405            )
 4406        });
 4407
 4408        // We remove any leading spaces
 4409        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4410        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4411            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4412        });
 4413        editor.join_lines(&JoinLines, window, cx);
 4414        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4415
 4416        // We don't insert a space for a line containing only spaces
 4417        editor.join_lines(&JoinLines, window, cx);
 4418        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4419
 4420        // We ignore any leading tabs
 4421        editor.join_lines(&JoinLines, window, cx);
 4422        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4423
 4424        editor
 4425    });
 4426}
 4427
 4428#[gpui::test]
 4429fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4430    init_test(cx, |_| {});
 4431
 4432    cx.add_window(|window, cx| {
 4433        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4434        let mut editor = build_editor(buffer.clone(), window, cx);
 4435        let buffer = buffer.read(cx).as_singleton().unwrap();
 4436
 4437        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4438            s.select_ranges([
 4439                Point::new(0, 2)..Point::new(1, 1),
 4440                Point::new(1, 2)..Point::new(1, 2),
 4441                Point::new(3, 1)..Point::new(3, 2),
 4442            ])
 4443        });
 4444
 4445        editor.join_lines(&JoinLines, window, cx);
 4446        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4447
 4448        assert_eq!(
 4449            editor.selections.ranges::<Point>(cx),
 4450            [
 4451                Point::new(0, 7)..Point::new(0, 7),
 4452                Point::new(1, 3)..Point::new(1, 3)
 4453            ]
 4454        );
 4455        editor
 4456    });
 4457}
 4458
 4459#[gpui::test]
 4460async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4461    init_test(cx, |_| {});
 4462
 4463    let mut cx = EditorTestContext::new(cx).await;
 4464
 4465    let diff_base = r#"
 4466        Line 0
 4467        Line 1
 4468        Line 2
 4469        Line 3
 4470        "#
 4471    .unindent();
 4472
 4473    cx.set_state(
 4474        &r#"
 4475        ˇLine 0
 4476        Line 1
 4477        Line 2
 4478        Line 3
 4479        "#
 4480        .unindent(),
 4481    );
 4482
 4483    cx.set_head_text(&diff_base);
 4484    executor.run_until_parked();
 4485
 4486    // Join lines
 4487    cx.update_editor(|editor, window, cx| {
 4488        editor.join_lines(&JoinLines, window, cx);
 4489    });
 4490    executor.run_until_parked();
 4491
 4492    cx.assert_editor_state(
 4493        &r#"
 4494        Line 0ˇ Line 1
 4495        Line 2
 4496        Line 3
 4497        "#
 4498        .unindent(),
 4499    );
 4500    // Join again
 4501    cx.update_editor(|editor, window, cx| {
 4502        editor.join_lines(&JoinLines, window, cx);
 4503    });
 4504    executor.run_until_parked();
 4505
 4506    cx.assert_editor_state(
 4507        &r#"
 4508        Line 0 Line 1ˇ Line 2
 4509        Line 3
 4510        "#
 4511        .unindent(),
 4512    );
 4513}
 4514
 4515#[gpui::test]
 4516async fn test_custom_newlines_cause_no_false_positive_diffs(
 4517    executor: BackgroundExecutor,
 4518    cx: &mut TestAppContext,
 4519) {
 4520    init_test(cx, |_| {});
 4521    let mut cx = EditorTestContext::new(cx).await;
 4522    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4523    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4524    executor.run_until_parked();
 4525
 4526    cx.update_editor(|editor, window, cx| {
 4527        let snapshot = editor.snapshot(window, cx);
 4528        assert_eq!(
 4529            snapshot
 4530                .buffer_snapshot
 4531                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 4532                .collect::<Vec<_>>(),
 4533            Vec::new(),
 4534            "Should not have any diffs for files with custom newlines"
 4535        );
 4536    });
 4537}
 4538
 4539#[gpui::test]
 4540async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4541    init_test(cx, |_| {});
 4542
 4543    let mut cx = EditorTestContext::new(cx).await;
 4544
 4545    // Test sort_lines_case_insensitive()
 4546    cx.set_state(indoc! {"
 4547        «z
 4548        y
 4549        x
 4550        Z
 4551        Y
 4552        Xˇ»
 4553    "});
 4554    cx.update_editor(|e, window, cx| {
 4555        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4556    });
 4557    cx.assert_editor_state(indoc! {"
 4558        «x
 4559        X
 4560        y
 4561        Y
 4562        z
 4563        Zˇ»
 4564    "});
 4565
 4566    // Test sort_lines_by_length()
 4567    //
 4568    // Demonstrates:
 4569    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4570    // - sort is stable
 4571    cx.set_state(indoc! {"
 4572        «123
 4573        æ
 4574        12
 4575 4576        1
 4577        æˇ»
 4578    "});
 4579    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4580    cx.assert_editor_state(indoc! {"
 4581        «æ
 4582 4583        1
 4584        æ
 4585        12
 4586        123ˇ»
 4587    "});
 4588
 4589    // Test reverse_lines()
 4590    cx.set_state(indoc! {"
 4591        «5
 4592        4
 4593        3
 4594        2
 4595        1ˇ»
 4596    "});
 4597    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4598    cx.assert_editor_state(indoc! {"
 4599        «1
 4600        2
 4601        3
 4602        4
 4603        5ˇ»
 4604    "});
 4605
 4606    // Skip testing shuffle_line()
 4607
 4608    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4609    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4610
 4611    // Don't manipulate when cursor is on single line, but expand the selection
 4612    cx.set_state(indoc! {"
 4613        ddˇdd
 4614        ccc
 4615        bb
 4616        a
 4617    "});
 4618    cx.update_editor(|e, window, cx| {
 4619        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4620    });
 4621    cx.assert_editor_state(indoc! {"
 4622        «ddddˇ»
 4623        ccc
 4624        bb
 4625        a
 4626    "});
 4627
 4628    // Basic manipulate case
 4629    // Start selection moves to column 0
 4630    // End of selection shrinks to fit shorter line
 4631    cx.set_state(indoc! {"
 4632        dd«d
 4633        ccc
 4634        bb
 4635        aaaaaˇ»
 4636    "});
 4637    cx.update_editor(|e, window, cx| {
 4638        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4639    });
 4640    cx.assert_editor_state(indoc! {"
 4641        «aaaaa
 4642        bb
 4643        ccc
 4644        dddˇ»
 4645    "});
 4646
 4647    // Manipulate case with newlines
 4648    cx.set_state(indoc! {"
 4649        dd«d
 4650        ccc
 4651
 4652        bb
 4653        aaaaa
 4654
 4655        ˇ»
 4656    "});
 4657    cx.update_editor(|e, window, cx| {
 4658        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4659    });
 4660    cx.assert_editor_state(indoc! {"
 4661        «
 4662
 4663        aaaaa
 4664        bb
 4665        ccc
 4666        dddˇ»
 4667
 4668    "});
 4669
 4670    // Adding new line
 4671    cx.set_state(indoc! {"
 4672        aa«a
 4673        bbˇ»b
 4674    "});
 4675    cx.update_editor(|e, window, cx| {
 4676        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4677    });
 4678    cx.assert_editor_state(indoc! {"
 4679        «aaa
 4680        bbb
 4681        added_lineˇ»
 4682    "});
 4683
 4684    // Removing line
 4685    cx.set_state(indoc! {"
 4686        aa«a
 4687        bbbˇ»
 4688    "});
 4689    cx.update_editor(|e, window, cx| {
 4690        e.manipulate_immutable_lines(window, cx, |lines| {
 4691            lines.pop();
 4692        })
 4693    });
 4694    cx.assert_editor_state(indoc! {"
 4695        «aaaˇ»
 4696    "});
 4697
 4698    // Removing all lines
 4699    cx.set_state(indoc! {"
 4700        aa«a
 4701        bbbˇ»
 4702    "});
 4703    cx.update_editor(|e, window, cx| {
 4704        e.manipulate_immutable_lines(window, cx, |lines| {
 4705            lines.drain(..);
 4706        })
 4707    });
 4708    cx.assert_editor_state(indoc! {"
 4709        ˇ
 4710    "});
 4711}
 4712
 4713#[gpui::test]
 4714async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4715    init_test(cx, |_| {});
 4716
 4717    let mut cx = EditorTestContext::new(cx).await;
 4718
 4719    // Consider continuous selection as single selection
 4720    cx.set_state(indoc! {"
 4721        Aaa«aa
 4722        cˇ»c«c
 4723        bb
 4724        aaaˇ»aa
 4725    "});
 4726    cx.update_editor(|e, window, cx| {
 4727        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4728    });
 4729    cx.assert_editor_state(indoc! {"
 4730        «Aaaaa
 4731        ccc
 4732        bb
 4733        aaaaaˇ»
 4734    "});
 4735
 4736    cx.set_state(indoc! {"
 4737        Aaa«aa
 4738        cˇ»c«c
 4739        bb
 4740        aaaˇ»aa
 4741    "});
 4742    cx.update_editor(|e, window, cx| {
 4743        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4744    });
 4745    cx.assert_editor_state(indoc! {"
 4746        «Aaaaa
 4747        ccc
 4748        bbˇ»
 4749    "});
 4750
 4751    // Consider non continuous selection as distinct dedup operations
 4752    cx.set_state(indoc! {"
 4753        «aaaaa
 4754        bb
 4755        aaaaa
 4756        aaaaaˇ»
 4757
 4758        aaa«aaˇ»
 4759    "});
 4760    cx.update_editor(|e, window, cx| {
 4761        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4762    });
 4763    cx.assert_editor_state(indoc! {"
 4764        «aaaaa
 4765        bbˇ»
 4766
 4767        «aaaaaˇ»
 4768    "});
 4769}
 4770
 4771#[gpui::test]
 4772async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4773    init_test(cx, |_| {});
 4774
 4775    let mut cx = EditorTestContext::new(cx).await;
 4776
 4777    cx.set_state(indoc! {"
 4778        «Aaa
 4779        aAa
 4780        Aaaˇ»
 4781    "});
 4782    cx.update_editor(|e, window, cx| {
 4783        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4784    });
 4785    cx.assert_editor_state(indoc! {"
 4786        «Aaa
 4787        aAaˇ»
 4788    "});
 4789
 4790    cx.set_state(indoc! {"
 4791        «Aaa
 4792        aAa
 4793        aaAˇ»
 4794    "});
 4795    cx.update_editor(|e, window, cx| {
 4796        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4797    });
 4798    cx.assert_editor_state(indoc! {"
 4799        «Aaaˇ»
 4800    "});
 4801}
 4802
 4803#[gpui::test]
 4804async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 4805    init_test(cx, |_| {});
 4806
 4807    let mut cx = EditorTestContext::new(cx).await;
 4808
 4809    let js_language = Arc::new(Language::new(
 4810        LanguageConfig {
 4811            name: "JavaScript".into(),
 4812            wrap_characters: Some(language::WrapCharactersConfig {
 4813                start_prefix: "<".into(),
 4814                start_suffix: ">".into(),
 4815                end_prefix: "</".into(),
 4816                end_suffix: ">".into(),
 4817            }),
 4818            ..LanguageConfig::default()
 4819        },
 4820        None,
 4821    ));
 4822
 4823    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4824
 4825    cx.set_state(indoc! {"
 4826        «testˇ»
 4827    "});
 4828    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4829    cx.assert_editor_state(indoc! {"
 4830        <«ˇ»>test</«ˇ»>
 4831    "});
 4832
 4833    cx.set_state(indoc! {"
 4834        «test
 4835         testˇ»
 4836    "});
 4837    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4838    cx.assert_editor_state(indoc! {"
 4839        <«ˇ»>test
 4840         test</«ˇ»>
 4841    "});
 4842
 4843    cx.set_state(indoc! {"
 4844        teˇst
 4845    "});
 4846    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4847    cx.assert_editor_state(indoc! {"
 4848        te<«ˇ»></«ˇ»>st
 4849    "});
 4850}
 4851
 4852#[gpui::test]
 4853async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 4854    init_test(cx, |_| {});
 4855
 4856    let mut cx = EditorTestContext::new(cx).await;
 4857
 4858    let js_language = Arc::new(Language::new(
 4859        LanguageConfig {
 4860            name: "JavaScript".into(),
 4861            wrap_characters: Some(language::WrapCharactersConfig {
 4862                start_prefix: "<".into(),
 4863                start_suffix: ">".into(),
 4864                end_prefix: "</".into(),
 4865                end_suffix: ">".into(),
 4866            }),
 4867            ..LanguageConfig::default()
 4868        },
 4869        None,
 4870    ));
 4871
 4872    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4873
 4874    cx.set_state(indoc! {"
 4875        «testˇ»
 4876        «testˇ» «testˇ»
 4877        «testˇ»
 4878    "});
 4879    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4880    cx.assert_editor_state(indoc! {"
 4881        <«ˇ»>test</«ˇ»>
 4882        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 4883        <«ˇ»>test</«ˇ»>
 4884    "});
 4885
 4886    cx.set_state(indoc! {"
 4887        «test
 4888         testˇ»
 4889        «test
 4890         testˇ»
 4891    "});
 4892    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4893    cx.assert_editor_state(indoc! {"
 4894        <«ˇ»>test
 4895         test</«ˇ»>
 4896        <«ˇ»>test
 4897         test</«ˇ»>
 4898    "});
 4899}
 4900
 4901#[gpui::test]
 4902async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 4903    init_test(cx, |_| {});
 4904
 4905    let mut cx = EditorTestContext::new(cx).await;
 4906
 4907    let plaintext_language = Arc::new(Language::new(
 4908        LanguageConfig {
 4909            name: "Plain Text".into(),
 4910            ..LanguageConfig::default()
 4911        },
 4912        None,
 4913    ));
 4914
 4915    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 4916
 4917    cx.set_state(indoc! {"
 4918        «testˇ»
 4919    "});
 4920    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4921    cx.assert_editor_state(indoc! {"
 4922      «testˇ»
 4923    "});
 4924}
 4925
 4926#[gpui::test]
 4927async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4928    init_test(cx, |_| {});
 4929
 4930    let mut cx = EditorTestContext::new(cx).await;
 4931
 4932    // Manipulate with multiple selections on a single line
 4933    cx.set_state(indoc! {"
 4934        dd«dd
 4935        cˇ»c«c
 4936        bb
 4937        aaaˇ»aa
 4938    "});
 4939    cx.update_editor(|e, window, cx| {
 4940        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4941    });
 4942    cx.assert_editor_state(indoc! {"
 4943        «aaaaa
 4944        bb
 4945        ccc
 4946        ddddˇ»
 4947    "});
 4948
 4949    // Manipulate with multiple disjoin selections
 4950    cx.set_state(indoc! {"
 4951 4952        4
 4953        3
 4954        2
 4955        1ˇ»
 4956
 4957        dd«dd
 4958        ccc
 4959        bb
 4960        aaaˇ»aa
 4961    "});
 4962    cx.update_editor(|e, window, cx| {
 4963        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4964    });
 4965    cx.assert_editor_state(indoc! {"
 4966        «1
 4967        2
 4968        3
 4969        4
 4970        5ˇ»
 4971
 4972        «aaaaa
 4973        bb
 4974        ccc
 4975        ddddˇ»
 4976    "});
 4977
 4978    // Adding lines on each selection
 4979    cx.set_state(indoc! {"
 4980 4981        1ˇ»
 4982
 4983        bb«bb
 4984        aaaˇ»aa
 4985    "});
 4986    cx.update_editor(|e, window, cx| {
 4987        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4988    });
 4989    cx.assert_editor_state(indoc! {"
 4990        «2
 4991        1
 4992        added lineˇ»
 4993
 4994        «bbbb
 4995        aaaaa
 4996        added lineˇ»
 4997    "});
 4998
 4999    // Removing lines on each selection
 5000    cx.set_state(indoc! {"
 5001 5002        1ˇ»
 5003
 5004        bb«bb
 5005        aaaˇ»aa
 5006    "});
 5007    cx.update_editor(|e, window, cx| {
 5008        e.manipulate_immutable_lines(window, cx, |lines| {
 5009            lines.pop();
 5010        })
 5011    });
 5012    cx.assert_editor_state(indoc! {"
 5013        «2ˇ»
 5014
 5015        «bbbbˇ»
 5016    "});
 5017}
 5018
 5019#[gpui::test]
 5020async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 5021    init_test(cx, |settings| {
 5022        settings.defaults.tab_size = NonZeroU32::new(3)
 5023    });
 5024
 5025    let mut cx = EditorTestContext::new(cx).await;
 5026
 5027    // MULTI SELECTION
 5028    // Ln.1 "«" tests empty lines
 5029    // Ln.9 tests just leading whitespace
 5030    cx.set_state(indoc! {"
 5031        «
 5032        abc                 // No indentationˇ»
 5033        «\tabc              // 1 tabˇ»
 5034        \t\tabc «      ˇ»   // 2 tabs
 5035        \t ab«c             // Tab followed by space
 5036         \tabc              // Space followed by tab (3 spaces should be the result)
 5037        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5038           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 5039        \t
 5040        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5041    "});
 5042    cx.update_editor(|e, window, cx| {
 5043        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5044    });
 5045    cx.assert_editor_state(
 5046        indoc! {"
 5047            «
 5048            abc                 // No indentation
 5049               abc              // 1 tab
 5050                  abc          // 2 tabs
 5051                abc             // Tab followed by space
 5052               abc              // Space followed by tab (3 spaces should be the result)
 5053                           abc   // Mixed indentation (tab conversion depends on the column)
 5054               abc         // Already space indented
 5055               ·
 5056               abc\tdef          // Only the leading tab is manipulatedˇ»
 5057        "}
 5058        .replace("·", "")
 5059        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5060    );
 5061
 5062    // Test on just a few lines, the others should remain unchanged
 5063    // Only lines (3, 5, 10, 11) should change
 5064    cx.set_state(
 5065        indoc! {"
 5066            ·
 5067            abc                 // No indentation
 5068            \tabcˇ               // 1 tab
 5069            \t\tabc             // 2 tabs
 5070            \t abcˇ              // Tab followed by space
 5071             \tabc              // Space followed by tab (3 spaces should be the result)
 5072            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5073               abc              // Already space indented
 5074            «\t
 5075            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5076        "}
 5077        .replace("·", "")
 5078        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5079    );
 5080    cx.update_editor(|e, window, cx| {
 5081        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5082    });
 5083    cx.assert_editor_state(
 5084        indoc! {"
 5085            ·
 5086            abc                 // No indentation
 5087            «   abc               // 1 tabˇ»
 5088            \t\tabc             // 2 tabs
 5089            «    abc              // Tab followed by spaceˇ»
 5090             \tabc              // Space followed by tab (3 spaces should be the result)
 5091            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5092               abc              // Already space indented
 5093            «   ·
 5094               abc\tdef          // Only the leading tab is manipulatedˇ»
 5095        "}
 5096        .replace("·", "")
 5097        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5098    );
 5099
 5100    // SINGLE SELECTION
 5101    // Ln.1 "«" tests empty lines
 5102    // Ln.9 tests just leading whitespace
 5103    cx.set_state(indoc! {"
 5104        «
 5105        abc                 // No indentation
 5106        \tabc               // 1 tab
 5107        \t\tabc             // 2 tabs
 5108        \t abc              // Tab followed by space
 5109         \tabc              // Space followed by tab (3 spaces should be the result)
 5110        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5111           abc              // Already space indented
 5112        \t
 5113        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5114    "});
 5115    cx.update_editor(|e, window, cx| {
 5116        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5117    });
 5118    cx.assert_editor_state(
 5119        indoc! {"
 5120            «
 5121            abc                 // No indentation
 5122               abc               // 1 tab
 5123                  abc             // 2 tabs
 5124                abc              // Tab followed by space
 5125               abc              // Space followed by tab (3 spaces should be the result)
 5126                           abc   // Mixed indentation (tab conversion depends on the column)
 5127               abc              // Already space indented
 5128               ·
 5129               abc\tdef          // Only the leading tab is manipulatedˇ»
 5130        "}
 5131        .replace("·", "")
 5132        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5133    );
 5134}
 5135
 5136#[gpui::test]
 5137async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5138    init_test(cx, |settings| {
 5139        settings.defaults.tab_size = NonZeroU32::new(3)
 5140    });
 5141
 5142    let mut cx = EditorTestContext::new(cx).await;
 5143
 5144    // MULTI SELECTION
 5145    // Ln.1 "«" tests empty lines
 5146    // Ln.11 tests just leading whitespace
 5147    cx.set_state(indoc! {"
 5148        «
 5149        abˇ»ˇc                 // No indentation
 5150         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5151          abc  «             // 2 spaces (< 3 so dont convert)
 5152           abc              // 3 spaces (convert)
 5153             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5154        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5155        «\t abc              // Tab followed by space
 5156         \tabc              // Space followed by tab (should be consumed due to tab)
 5157        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5158           \tˇ»  «\t
 5159           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5160    "});
 5161    cx.update_editor(|e, window, cx| {
 5162        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5163    });
 5164    cx.assert_editor_state(indoc! {"
 5165        «
 5166        abc                 // No indentation
 5167         abc                // 1 space (< 3 so dont convert)
 5168          abc               // 2 spaces (< 3 so dont convert)
 5169        \tabc              // 3 spaces (convert)
 5170        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5171        \t\t\tabc           // 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\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5175        \t\t\t
 5176        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5177    "});
 5178
 5179    // Test on just a few lines, the other should remain unchanged
 5180    // Only lines (4, 8, 11, 12) should change
 5181    cx.set_state(
 5182        indoc! {"
 5183            ·
 5184            abc                 // No indentation
 5185             abc                // 1 space (< 3 so dont convert)
 5186              abc               // 2 spaces (< 3 so dont convert)
 5187            «   abc              // 3 spaces (convert)ˇ»
 5188                 abc            // 5 spaces (1 tab + 2 spaces)
 5189            \t\t\tabc           // Already tab indented
 5190            \t abc              // Tab followed by space
 5191             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5192               \t\t  \tabc      // Mixed indentation
 5193            \t \t  \t   \tabc   // Mixed indentation
 5194               \t  \tˇ
 5195            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5196        "}
 5197        .replace("·", "")
 5198        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5199    );
 5200    cx.update_editor(|e, window, cx| {
 5201        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5202    });
 5203    cx.assert_editor_state(
 5204        indoc! {"
 5205            ·
 5206            abc                 // No indentation
 5207             abc                // 1 space (< 3 so dont convert)
 5208              abc               // 2 spaces (< 3 so dont convert)
 5209            «\tabc              // 3 spaces (convert)ˇ»
 5210                 abc            // 5 spaces (1 tab + 2 spaces)
 5211            \t\t\tabc           // Already tab indented
 5212            \t abc              // Tab followed by space
 5213            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5214               \t\t  \tabc      // Mixed indentation
 5215            \t \t  \t   \tabc   // Mixed indentation
 5216            «\t\t\t
 5217            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5218        "}
 5219        .replace("·", "")
 5220        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5221    );
 5222
 5223    // SINGLE SELECTION
 5224    // Ln.1 "«" tests empty lines
 5225    // Ln.11 tests just leading whitespace
 5226    cx.set_state(indoc! {"
 5227        «
 5228        abc                 // No indentation
 5229         abc                // 1 space (< 3 so dont convert)
 5230          abc               // 2 spaces (< 3 so dont convert)
 5231           abc              // 3 spaces (convert)
 5232             abc            // 5 spaces (1 tab + 2 spaces)
 5233        \t\t\tabc           // Already tab indented
 5234        \t abc              // Tab followed by space
 5235         \tabc              // Space followed by tab (should be consumed due to tab)
 5236        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5237           \t  \t
 5238           abc   \t         // Only the leading spaces should be convertedˇ»
 5239    "});
 5240    cx.update_editor(|e, window, cx| {
 5241        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5242    });
 5243    cx.assert_editor_state(indoc! {"
 5244        «
 5245        abc                 // No indentation
 5246         abc                // 1 space (< 3 so dont convert)
 5247          abc               // 2 spaces (< 3 so dont convert)
 5248        \tabc              // 3 spaces (convert)
 5249        \t  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\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5254        \t\t\t
 5255        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5256    "});
 5257}
 5258
 5259#[gpui::test]
 5260async fn test_toggle_case(cx: &mut TestAppContext) {
 5261    init_test(cx, |_| {});
 5262
 5263    let mut cx = EditorTestContext::new(cx).await;
 5264
 5265    // If all lower case -> upper case
 5266    cx.set_state(indoc! {"
 5267        «hello worldˇ»
 5268    "});
 5269    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5270    cx.assert_editor_state(indoc! {"
 5271        «HELLO WORLDˇ»
 5272    "});
 5273
 5274    // If all upper case -> lower case
 5275    cx.set_state(indoc! {"
 5276        «HELLO WORLDˇ»
 5277    "});
 5278    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5279    cx.assert_editor_state(indoc! {"
 5280        «hello worldˇ»
 5281    "});
 5282
 5283    // If any upper case characters are identified -> lower case
 5284    // This matches JetBrains IDEs
 5285    cx.set_state(indoc! {"
 5286        «hEllo worldˇ»
 5287    "});
 5288    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5289    cx.assert_editor_state(indoc! {"
 5290        «hello worldˇ»
 5291    "});
 5292}
 5293
 5294#[gpui::test]
 5295async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5296    init_test(cx, |_| {});
 5297
 5298    let mut cx = EditorTestContext::new(cx).await;
 5299
 5300    cx.set_state(indoc! {"
 5301        «implement-windows-supportˇ»
 5302    "});
 5303    cx.update_editor(|e, window, cx| {
 5304        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5305    });
 5306    cx.assert_editor_state(indoc! {"
 5307        «Implement windows supportˇ»
 5308    "});
 5309}
 5310
 5311#[gpui::test]
 5312async fn test_manipulate_text(cx: &mut TestAppContext) {
 5313    init_test(cx, |_| {});
 5314
 5315    let mut cx = EditorTestContext::new(cx).await;
 5316
 5317    // Test convert_to_upper_case()
 5318    cx.set_state(indoc! {"
 5319        «hello worldˇ»
 5320    "});
 5321    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5322    cx.assert_editor_state(indoc! {"
 5323        «HELLO WORLDˇ»
 5324    "});
 5325
 5326    // Test convert_to_lower_case()
 5327    cx.set_state(indoc! {"
 5328        «HELLO WORLDˇ»
 5329    "});
 5330    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5331    cx.assert_editor_state(indoc! {"
 5332        «hello worldˇ»
 5333    "});
 5334
 5335    // Test multiple line, single selection case
 5336    cx.set_state(indoc! {"
 5337        «The quick brown
 5338        fox jumps over
 5339        the lazy dogˇ»
 5340    "});
 5341    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5342    cx.assert_editor_state(indoc! {"
 5343        «The Quick Brown
 5344        Fox Jumps Over
 5345        The Lazy Dogˇ»
 5346    "});
 5347
 5348    // Test multiple line, single selection case
 5349    cx.set_state(indoc! {"
 5350        «The quick brown
 5351        fox jumps over
 5352        the lazy dogˇ»
 5353    "});
 5354    cx.update_editor(|e, window, cx| {
 5355        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5356    });
 5357    cx.assert_editor_state(indoc! {"
 5358        «TheQuickBrown
 5359        FoxJumpsOver
 5360        TheLazyDogˇ»
 5361    "});
 5362
 5363    // From here on out, test more complex cases of manipulate_text()
 5364
 5365    // Test no selection case - should affect words cursors are in
 5366    // Cursor at beginning, middle, and end of word
 5367    cx.set_state(indoc! {"
 5368        ˇhello big beauˇtiful worldˇ
 5369    "});
 5370    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5371    cx.assert_editor_state(indoc! {"
 5372        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5373    "});
 5374
 5375    // Test multiple selections on a single line and across multiple lines
 5376    cx.set_state(indoc! {"
 5377        «Theˇ» quick «brown
 5378        foxˇ» jumps «overˇ»
 5379        the «lazyˇ» dog
 5380    "});
 5381    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5382    cx.assert_editor_state(indoc! {"
 5383        «THEˇ» quick «BROWN
 5384        FOXˇ» jumps «OVERˇ»
 5385        the «LAZYˇ» dog
 5386    "});
 5387
 5388    // Test case where text length grows
 5389    cx.set_state(indoc! {"
 5390        «tschüߡ»
 5391    "});
 5392    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5393    cx.assert_editor_state(indoc! {"
 5394        «TSCHÜSSˇ»
 5395    "});
 5396
 5397    // Test to make sure we don't crash when text shrinks
 5398    cx.set_state(indoc! {"
 5399        aaa_bbbˇ
 5400    "});
 5401    cx.update_editor(|e, window, cx| {
 5402        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5403    });
 5404    cx.assert_editor_state(indoc! {"
 5405        «aaaBbbˇ»
 5406    "});
 5407
 5408    // Test to make sure we all aware of the fact that each word can grow and shrink
 5409    // Final selections should be aware of this fact
 5410    cx.set_state(indoc! {"
 5411        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5412    "});
 5413    cx.update_editor(|e, window, cx| {
 5414        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5415    });
 5416    cx.assert_editor_state(indoc! {"
 5417        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5418    "});
 5419
 5420    cx.set_state(indoc! {"
 5421        «hElLo, WoRld!ˇ»
 5422    "});
 5423    cx.update_editor(|e, window, cx| {
 5424        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5425    });
 5426    cx.assert_editor_state(indoc! {"
 5427        «HeLlO, wOrLD!ˇ»
 5428    "});
 5429
 5430    // Test selections with `line_mode() = true`.
 5431    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 5432    cx.set_state(indoc! {"
 5433        «The quick brown
 5434        fox jumps over
 5435        tˇ»he lazy dog
 5436    "});
 5437    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5438    cx.assert_editor_state(indoc! {"
 5439        «THE QUICK BROWN
 5440        FOX JUMPS OVER
 5441        THE LAZY DOGˇ»
 5442    "});
 5443}
 5444
 5445#[gpui::test]
 5446fn test_duplicate_line(cx: &mut TestAppContext) {
 5447    init_test(cx, |_| {});
 5448
 5449    let editor = cx.add_window(|window, cx| {
 5450        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5451        build_editor(buffer, window, cx)
 5452    });
 5453    _ = editor.update(cx, |editor, window, cx| {
 5454        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5455            s.select_display_ranges([
 5456                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5457                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5458                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5459                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5460            ])
 5461        });
 5462        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5463        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5464        assert_eq!(
 5465            editor.selections.display_ranges(cx),
 5466            vec![
 5467                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5468                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5469                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5470                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5471            ]
 5472        );
 5473    });
 5474
 5475    let editor = cx.add_window(|window, cx| {
 5476        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5477        build_editor(buffer, window, cx)
 5478    });
 5479    _ = editor.update(cx, |editor, window, cx| {
 5480        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5481            s.select_display_ranges([
 5482                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5483                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5484            ])
 5485        });
 5486        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5487        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5488        assert_eq!(
 5489            editor.selections.display_ranges(cx),
 5490            vec![
 5491                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5492                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5493            ]
 5494        );
 5495    });
 5496
 5497    // With `move_upwards` the selections stay in place, except for
 5498    // the lines inserted above them
 5499    let editor = cx.add_window(|window, cx| {
 5500        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5501        build_editor(buffer, window, cx)
 5502    });
 5503    _ = editor.update(cx, |editor, window, cx| {
 5504        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5505            s.select_display_ranges([
 5506                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5507                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5508                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5509                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5510            ])
 5511        });
 5512        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5513        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5514        assert_eq!(
 5515            editor.selections.display_ranges(cx),
 5516            vec![
 5517                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5518                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5519                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5520                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5521            ]
 5522        );
 5523    });
 5524
 5525    let editor = cx.add_window(|window, cx| {
 5526        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5527        build_editor(buffer, window, cx)
 5528    });
 5529    _ = editor.update(cx, |editor, window, cx| {
 5530        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5531            s.select_display_ranges([
 5532                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5533                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5534            ])
 5535        });
 5536        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5537        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5538        assert_eq!(
 5539            editor.selections.display_ranges(cx),
 5540            vec![
 5541                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5542                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5543            ]
 5544        );
 5545    });
 5546
 5547    let editor = cx.add_window(|window, cx| {
 5548        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5549        build_editor(buffer, window, cx)
 5550    });
 5551    _ = editor.update(cx, |editor, window, cx| {
 5552        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5553            s.select_display_ranges([
 5554                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5555                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5556            ])
 5557        });
 5558        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5559        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5560        assert_eq!(
 5561            editor.selections.display_ranges(cx),
 5562            vec![
 5563                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5564                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5565            ]
 5566        );
 5567    });
 5568}
 5569
 5570#[gpui::test]
 5571fn test_move_line_up_down(cx: &mut TestAppContext) {
 5572    init_test(cx, |_| {});
 5573
 5574    let editor = cx.add_window(|window, cx| {
 5575        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5576        build_editor(buffer, window, cx)
 5577    });
 5578    _ = editor.update(cx, |editor, window, cx| {
 5579        editor.fold_creases(
 5580            vec![
 5581                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5582                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5583                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5584            ],
 5585            true,
 5586            window,
 5587            cx,
 5588        );
 5589        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5590            s.select_display_ranges([
 5591                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5592                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5593                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5594                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5595            ])
 5596        });
 5597        assert_eq!(
 5598            editor.display_text(cx),
 5599            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5600        );
 5601
 5602        editor.move_line_up(&MoveLineUp, window, cx);
 5603        assert_eq!(
 5604            editor.display_text(cx),
 5605            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5606        );
 5607        assert_eq!(
 5608            editor.selections.display_ranges(cx),
 5609            vec![
 5610                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5611                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5612                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5613                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5614            ]
 5615        );
 5616    });
 5617
 5618    _ = editor.update(cx, |editor, window, cx| {
 5619        editor.move_line_down(&MoveLineDown, window, cx);
 5620        assert_eq!(
 5621            editor.display_text(cx),
 5622            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5623        );
 5624        assert_eq!(
 5625            editor.selections.display_ranges(cx),
 5626            vec![
 5627                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5628                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5629                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5630                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 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\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5640        );
 5641        assert_eq!(
 5642            editor.selections.display_ranges(cx),
 5643            vec![
 5644                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 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_up(&MoveLineUp, window, cx);
 5654        assert_eq!(
 5655            editor.display_text(cx),
 5656            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5657        );
 5658        assert_eq!(
 5659            editor.selections.display_ranges(cx),
 5660            vec![
 5661                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5662                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5663                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5664                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5665            ]
 5666        );
 5667    });
 5668}
 5669
 5670#[gpui::test]
 5671fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5672    init_test(cx, |_| {});
 5673    let editor = cx.add_window(|window, cx| {
 5674        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5675        build_editor(buffer, window, cx)
 5676    });
 5677    _ = editor.update(cx, |editor, window, cx| {
 5678        editor.fold_creases(
 5679            vec![Crease::simple(
 5680                Point::new(6, 4)..Point::new(7, 4),
 5681                FoldPlaceholder::test(),
 5682            )],
 5683            true,
 5684            window,
 5685            cx,
 5686        );
 5687        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5688            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5689        });
 5690        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5691        editor.move_line_up(&MoveLineUp, window, cx);
 5692        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5693        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5694    });
 5695}
 5696
 5697#[gpui::test]
 5698fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5699    init_test(cx, |_| {});
 5700
 5701    let editor = cx.add_window(|window, cx| {
 5702        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5703        build_editor(buffer, window, cx)
 5704    });
 5705    _ = editor.update(cx, |editor, window, cx| {
 5706        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5707        editor.insert_blocks(
 5708            [BlockProperties {
 5709                style: BlockStyle::Fixed,
 5710                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5711                height: Some(1),
 5712                render: Arc::new(|_| div().into_any()),
 5713                priority: 0,
 5714            }],
 5715            Some(Autoscroll::fit()),
 5716            cx,
 5717        );
 5718        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5719            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5720        });
 5721        editor.move_line_down(&MoveLineDown, window, cx);
 5722    });
 5723}
 5724
 5725#[gpui::test]
 5726async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5727    init_test(cx, |_| {});
 5728
 5729    let mut cx = EditorTestContext::new(cx).await;
 5730    cx.set_state(
 5731        &"
 5732            ˇzero
 5733            one
 5734            two
 5735            three
 5736            four
 5737            five
 5738        "
 5739        .unindent(),
 5740    );
 5741
 5742    // Create a four-line block that replaces three lines of text.
 5743    cx.update_editor(|editor, window, cx| {
 5744        let snapshot = editor.snapshot(window, cx);
 5745        let snapshot = &snapshot.buffer_snapshot;
 5746        let placement = BlockPlacement::Replace(
 5747            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5748        );
 5749        editor.insert_blocks(
 5750            [BlockProperties {
 5751                placement,
 5752                height: Some(4),
 5753                style: BlockStyle::Sticky,
 5754                render: Arc::new(|_| gpui::div().into_any_element()),
 5755                priority: 0,
 5756            }],
 5757            None,
 5758            cx,
 5759        );
 5760    });
 5761
 5762    // Move down so that the cursor touches the block.
 5763    cx.update_editor(|editor, window, cx| {
 5764        editor.move_down(&Default::default(), window, cx);
 5765    });
 5766    cx.assert_editor_state(
 5767        &"
 5768            zero
 5769            «one
 5770            two
 5771            threeˇ»
 5772            four
 5773            five
 5774        "
 5775        .unindent(),
 5776    );
 5777
 5778    // Move down past the block.
 5779    cx.update_editor(|editor, window, cx| {
 5780        editor.move_down(&Default::default(), window, cx);
 5781    });
 5782    cx.assert_editor_state(
 5783        &"
 5784            zero
 5785            one
 5786            two
 5787            three
 5788            ˇfour
 5789            five
 5790        "
 5791        .unindent(),
 5792    );
 5793}
 5794
 5795#[gpui::test]
 5796fn test_transpose(cx: &mut TestAppContext) {
 5797    init_test(cx, |_| {});
 5798
 5799    _ = cx.add_window(|window, cx| {
 5800        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5801        editor.set_style(EditorStyle::default(), window, cx);
 5802        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5803            s.select_ranges([1..1])
 5804        });
 5805        editor.transpose(&Default::default(), window, cx);
 5806        assert_eq!(editor.text(cx), "bac");
 5807        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5808
 5809        editor.transpose(&Default::default(), window, cx);
 5810        assert_eq!(editor.text(cx), "bca");
 5811        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5812
 5813        editor.transpose(&Default::default(), window, cx);
 5814        assert_eq!(editor.text(cx), "bac");
 5815        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5816
 5817        editor
 5818    });
 5819
 5820    _ = cx.add_window(|window, cx| {
 5821        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5822        editor.set_style(EditorStyle::default(), window, cx);
 5823        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5824            s.select_ranges([3..3])
 5825        });
 5826        editor.transpose(&Default::default(), window, cx);
 5827        assert_eq!(editor.text(cx), "acb\nde");
 5828        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5829
 5830        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5831            s.select_ranges([4..4])
 5832        });
 5833        editor.transpose(&Default::default(), window, cx);
 5834        assert_eq!(editor.text(cx), "acbd\ne");
 5835        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5836
 5837        editor.transpose(&Default::default(), window, cx);
 5838        assert_eq!(editor.text(cx), "acbde\n");
 5839        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5840
 5841        editor.transpose(&Default::default(), window, cx);
 5842        assert_eq!(editor.text(cx), "acbd\ne");
 5843        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5844
 5845        editor
 5846    });
 5847
 5848    _ = cx.add_window(|window, cx| {
 5849        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5850        editor.set_style(EditorStyle::default(), window, cx);
 5851        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5852            s.select_ranges([1..1, 2..2, 4..4])
 5853        });
 5854        editor.transpose(&Default::default(), window, cx);
 5855        assert_eq!(editor.text(cx), "bacd\ne");
 5856        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5857
 5858        editor.transpose(&Default::default(), window, cx);
 5859        assert_eq!(editor.text(cx), "bcade\n");
 5860        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5861
 5862        editor.transpose(&Default::default(), window, cx);
 5863        assert_eq!(editor.text(cx), "bcda\ne");
 5864        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5865
 5866        editor.transpose(&Default::default(), window, cx);
 5867        assert_eq!(editor.text(cx), "bcade\n");
 5868        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5869
 5870        editor.transpose(&Default::default(), window, cx);
 5871        assert_eq!(editor.text(cx), "bcaed\n");
 5872        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5873
 5874        editor
 5875    });
 5876
 5877    _ = cx.add_window(|window, cx| {
 5878        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5879        editor.set_style(EditorStyle::default(), window, cx);
 5880        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5881            s.select_ranges([4..4])
 5882        });
 5883        editor.transpose(&Default::default(), window, cx);
 5884        assert_eq!(editor.text(cx), "🏀🍐✋");
 5885        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5886
 5887        editor.transpose(&Default::default(), window, cx);
 5888        assert_eq!(editor.text(cx), "🏀✋🍐");
 5889        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5890
 5891        editor.transpose(&Default::default(), window, cx);
 5892        assert_eq!(editor.text(cx), "🏀🍐✋");
 5893        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5894
 5895        editor
 5896    });
 5897}
 5898
 5899#[gpui::test]
 5900async fn test_rewrap(cx: &mut TestAppContext) {
 5901    init_test(cx, |settings| {
 5902        settings.languages.0.extend([
 5903            (
 5904                "Markdown".into(),
 5905                LanguageSettingsContent {
 5906                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5907                    preferred_line_length: Some(40),
 5908                    ..Default::default()
 5909                },
 5910            ),
 5911            (
 5912                "Plain Text".into(),
 5913                LanguageSettingsContent {
 5914                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5915                    preferred_line_length: Some(40),
 5916                    ..Default::default()
 5917                },
 5918            ),
 5919            (
 5920                "C++".into(),
 5921                LanguageSettingsContent {
 5922                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5923                    preferred_line_length: Some(40),
 5924                    ..Default::default()
 5925                },
 5926            ),
 5927            (
 5928                "Python".into(),
 5929                LanguageSettingsContent {
 5930                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5931                    preferred_line_length: Some(40),
 5932                    ..Default::default()
 5933                },
 5934            ),
 5935            (
 5936                "Rust".into(),
 5937                LanguageSettingsContent {
 5938                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5939                    preferred_line_length: Some(40),
 5940                    ..Default::default()
 5941                },
 5942            ),
 5943        ])
 5944    });
 5945
 5946    let mut cx = EditorTestContext::new(cx).await;
 5947
 5948    let cpp_language = Arc::new(Language::new(
 5949        LanguageConfig {
 5950            name: "C++".into(),
 5951            line_comments: vec!["// ".into()],
 5952            ..LanguageConfig::default()
 5953        },
 5954        None,
 5955    ));
 5956    let python_language = Arc::new(Language::new(
 5957        LanguageConfig {
 5958            name: "Python".into(),
 5959            line_comments: vec!["# ".into()],
 5960            ..LanguageConfig::default()
 5961        },
 5962        None,
 5963    ));
 5964    let markdown_language = Arc::new(Language::new(
 5965        LanguageConfig {
 5966            name: "Markdown".into(),
 5967            rewrap_prefixes: vec![
 5968                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 5969                regex::Regex::new("[-*+]\\s+").unwrap(),
 5970            ],
 5971            ..LanguageConfig::default()
 5972        },
 5973        None,
 5974    ));
 5975    let rust_language = Arc::new(
 5976        Language::new(
 5977            LanguageConfig {
 5978                name: "Rust".into(),
 5979                line_comments: vec!["// ".into(), "/// ".into()],
 5980                ..LanguageConfig::default()
 5981            },
 5982            Some(tree_sitter_rust::LANGUAGE.into()),
 5983        )
 5984        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 5985        .unwrap(),
 5986    );
 5987
 5988    let plaintext_language = Arc::new(Language::new(
 5989        LanguageConfig {
 5990            name: "Plain Text".into(),
 5991            ..LanguageConfig::default()
 5992        },
 5993        None,
 5994    ));
 5995
 5996    // Test basic rewrapping of a long line with a cursor
 5997    assert_rewrap(
 5998        indoc! {"
 5999            // ˇThis is a long comment that needs to be wrapped.
 6000        "},
 6001        indoc! {"
 6002            // ˇThis is a long comment that needs to
 6003            // be wrapped.
 6004        "},
 6005        cpp_language.clone(),
 6006        &mut cx,
 6007    );
 6008
 6009    // Test rewrapping a full selection
 6010    assert_rewrap(
 6011        indoc! {"
 6012            «// This selected long comment needs to be wrapped.ˇ»"
 6013        },
 6014        indoc! {"
 6015            «// This selected long comment needs to
 6016            // be wrapped.ˇ»"
 6017        },
 6018        cpp_language.clone(),
 6019        &mut cx,
 6020    );
 6021
 6022    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 6023    assert_rewrap(
 6024        indoc! {"
 6025            // ˇThis is the first line.
 6026            // Thisˇ is the second line.
 6027            // This is the thirdˇ line, all part of one paragraph.
 6028         "},
 6029        indoc! {"
 6030            // ˇThis is the first line. Thisˇ is the
 6031            // second line. This is the thirdˇ line,
 6032            // all part of one paragraph.
 6033         "},
 6034        cpp_language.clone(),
 6035        &mut cx,
 6036    );
 6037
 6038    // Test multiple cursors in different paragraphs trigger separate rewraps
 6039    assert_rewrap(
 6040        indoc! {"
 6041            // ˇThis is the first paragraph, first line.
 6042            // ˇThis is the first paragraph, second line.
 6043
 6044            // ˇThis is the second paragraph, first line.
 6045            // ˇThis is the second paragraph, second line.
 6046        "},
 6047        indoc! {"
 6048            // ˇThis is the first paragraph, first
 6049            // line. ˇThis is the first paragraph,
 6050            // second line.
 6051
 6052            // ˇThis is the second paragraph, first
 6053            // line. ˇThis is the second paragraph,
 6054            // second line.
 6055        "},
 6056        cpp_language.clone(),
 6057        &mut cx,
 6058    );
 6059
 6060    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 6061    assert_rewrap(
 6062        indoc! {"
 6063            «// A regular long long comment to be wrapped.
 6064            /// A documentation long comment to be wrapped.ˇ»
 6065          "},
 6066        indoc! {"
 6067            «// A regular long long comment to be
 6068            // wrapped.
 6069            /// A documentation long comment to be
 6070            /// wrapped.ˇ»
 6071          "},
 6072        rust_language.clone(),
 6073        &mut cx,
 6074    );
 6075
 6076    // Test that change in indentation level trigger seperate rewraps
 6077    assert_rewrap(
 6078        indoc! {"
 6079            fn foo() {
 6080                «// This is a long comment at the base indent.
 6081                    // This is a long comment at the next indent.ˇ»
 6082            }
 6083        "},
 6084        indoc! {"
 6085            fn foo() {
 6086                «// This is a long comment at the
 6087                // base indent.
 6088                    // This is a long comment at the
 6089                    // next indent.ˇ»
 6090            }
 6091        "},
 6092        rust_language.clone(),
 6093        &mut cx,
 6094    );
 6095
 6096    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6097    assert_rewrap(
 6098        indoc! {"
 6099            # ˇThis is a long comment using a pound sign.
 6100        "},
 6101        indoc! {"
 6102            # ˇThis is a long comment using a pound
 6103            # sign.
 6104        "},
 6105        python_language,
 6106        &mut cx,
 6107    );
 6108
 6109    // Test rewrapping only affects comments, not code even when selected
 6110    assert_rewrap(
 6111        indoc! {"
 6112            «/// This doc comment is long and should be wrapped.
 6113            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6114        "},
 6115        indoc! {"
 6116            «/// This doc comment is long and should
 6117            /// be wrapped.
 6118            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6119        "},
 6120        rust_language.clone(),
 6121        &mut cx,
 6122    );
 6123
 6124    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6125    assert_rewrap(
 6126        indoc! {"
 6127            # Header
 6128
 6129            A long long long line of markdown text to wrap.ˇ
 6130         "},
 6131        indoc! {"
 6132            # Header
 6133
 6134            A long long long line of markdown text
 6135            to wrap.ˇ
 6136         "},
 6137        markdown_language.clone(),
 6138        &mut cx,
 6139    );
 6140
 6141    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6142    assert_rewrap(
 6143        indoc! {"
 6144            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6145            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6146            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6147        "},
 6148        indoc! {"
 6149            «1. This is a numbered list item that is
 6150               very long and needs to be wrapped
 6151               properly.
 6152            2. This is a numbered list item that is
 6153               very long and needs to be wrapped
 6154               properly.
 6155            - This is an unordered list item that is
 6156              also very long and should not merge
 6157              with the numbered item.ˇ»
 6158        "},
 6159        markdown_language.clone(),
 6160        &mut cx,
 6161    );
 6162
 6163    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6164    assert_rewrap(
 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 with
 6174            the numbered item.ˇ»
 6175        "},
 6176        indoc! {"
 6177            «1. This is a numbered list item that is
 6178               very long and needs to be wrapped
 6179               properly.
 6180            2. This is a numbered list item that is
 6181               very long and needs to be wrapped
 6182               properly.
 6183            - This is an unordered list item that is
 6184              also very long and should not merge
 6185              with the numbered item.ˇ»
 6186        "},
 6187        markdown_language.clone(),
 6188        &mut cx,
 6189    );
 6190
 6191    // Test that rewrapping maintain indents even when they already exists.
 6192    assert_rewrap(
 6193        indoc! {"
 6194            «1. This is a numbered list
 6195               item that is very long and needs to be wrapped properly.
 6196            2. This is a numbered list
 6197               item that is very long and needs to be wrapped properly.
 6198            - This is an unordered list item that is also very long and
 6199              should not merge with the numbered item.ˇ»
 6200        "},
 6201        indoc! {"
 6202            «1. This is a numbered list item that is
 6203               very long and needs to be wrapped
 6204               properly.
 6205            2. This is a numbered list item that is
 6206               very long and needs to be wrapped
 6207               properly.
 6208            - This is an unordered list item that is
 6209              also very long and should not merge
 6210              with the numbered item.ˇ»
 6211        "},
 6212        markdown_language,
 6213        &mut cx,
 6214    );
 6215
 6216    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6217    assert_rewrap(
 6218        indoc! {"
 6219            ˇThis is a very long line of plain text that will be wrapped.
 6220        "},
 6221        indoc! {"
 6222            ˇThis is a very long line of plain text
 6223            that will be wrapped.
 6224        "},
 6225        plaintext_language.clone(),
 6226        &mut cx,
 6227    );
 6228
 6229    // Test that non-commented code acts as a paragraph boundary within a selection
 6230    assert_rewrap(
 6231        indoc! {"
 6232               «// This is the first long comment block to be wrapped.
 6233               fn my_func(a: u32);
 6234               // This is the second long comment block to be wrapped.ˇ»
 6235           "},
 6236        indoc! {"
 6237               «// This is the first long comment block
 6238               // to be wrapped.
 6239               fn my_func(a: u32);
 6240               // This is the second long comment block
 6241               // to be wrapped.ˇ»
 6242           "},
 6243        rust_language,
 6244        &mut cx,
 6245    );
 6246
 6247    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6248    assert_rewrap(
 6249        indoc! {"
 6250            «ˇThis is a very long line that will be wrapped.
 6251
 6252            This is another paragraph in the same selection.»
 6253
 6254            «\tThis is a very long indented line that will be wrapped.ˇ»
 6255         "},
 6256        indoc! {"
 6257            «ˇThis is a very long line that will be
 6258            wrapped.
 6259
 6260            This is another paragraph in the same
 6261            selection.»
 6262
 6263            «\tThis is a very long indented line
 6264            \tthat will be wrapped.ˇ»
 6265         "},
 6266        plaintext_language,
 6267        &mut cx,
 6268    );
 6269
 6270    // Test that an empty comment line acts as a paragraph boundary
 6271    assert_rewrap(
 6272        indoc! {"
 6273            // ˇThis is a long comment that will be wrapped.
 6274            //
 6275            // And this is another long comment that will also be wrapped.ˇ
 6276         "},
 6277        indoc! {"
 6278            // ˇThis is a long comment that will be
 6279            // wrapped.
 6280            //
 6281            // And this is another long comment that
 6282            // will also be wrapped.ˇ
 6283         "},
 6284        cpp_language,
 6285        &mut cx,
 6286    );
 6287
 6288    #[track_caller]
 6289    fn assert_rewrap(
 6290        unwrapped_text: &str,
 6291        wrapped_text: &str,
 6292        language: Arc<Language>,
 6293        cx: &mut EditorTestContext,
 6294    ) {
 6295        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6296        cx.set_state(unwrapped_text);
 6297        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6298        cx.assert_editor_state(wrapped_text);
 6299    }
 6300}
 6301
 6302#[gpui::test]
 6303async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6304    init_test(cx, |settings| {
 6305        settings.languages.0.extend([(
 6306            "Rust".into(),
 6307            LanguageSettingsContent {
 6308                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6309                preferred_line_length: Some(40),
 6310                ..Default::default()
 6311            },
 6312        )])
 6313    });
 6314
 6315    let mut cx = EditorTestContext::new(cx).await;
 6316
 6317    let rust_lang = Arc::new(
 6318        Language::new(
 6319            LanguageConfig {
 6320                name: "Rust".into(),
 6321                line_comments: vec!["// ".into()],
 6322                block_comment: Some(BlockCommentConfig {
 6323                    start: "/*".into(),
 6324                    end: "*/".into(),
 6325                    prefix: "* ".into(),
 6326                    tab_size: 1,
 6327                }),
 6328                documentation_comment: Some(BlockCommentConfig {
 6329                    start: "/**".into(),
 6330                    end: "*/".into(),
 6331                    prefix: "* ".into(),
 6332                    tab_size: 1,
 6333                }),
 6334
 6335                ..LanguageConfig::default()
 6336            },
 6337            Some(tree_sitter_rust::LANGUAGE.into()),
 6338        )
 6339        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6340        .unwrap(),
 6341    );
 6342
 6343    // regular block comment
 6344    assert_rewrap(
 6345        indoc! {"
 6346            /*
 6347             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6348             */
 6349            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6350        "},
 6351        indoc! {"
 6352            /*
 6353             *ˇ Lorem ipsum dolor sit amet,
 6354             * consectetur adipiscing elit.
 6355             */
 6356            /*
 6357             *ˇ Lorem ipsum dolor sit amet,
 6358             * consectetur adipiscing elit.
 6359             */
 6360        "},
 6361        rust_lang.clone(),
 6362        &mut cx,
 6363    );
 6364
 6365    // indent is respected
 6366    assert_rewrap(
 6367        indoc! {"
 6368            {}
 6369                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6370        "},
 6371        indoc! {"
 6372            {}
 6373                /*
 6374                 *ˇ Lorem ipsum dolor sit amet,
 6375                 * consectetur adipiscing elit.
 6376                 */
 6377        "},
 6378        rust_lang.clone(),
 6379        &mut cx,
 6380    );
 6381
 6382    // short block comments with inline delimiters
 6383    assert_rewrap(
 6384        indoc! {"
 6385            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6386            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6387             */
 6388            /*
 6389             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6390        "},
 6391        indoc! {"
 6392            /*
 6393             *ˇ Lorem ipsum dolor sit amet,
 6394             * consectetur adipiscing elit.
 6395             */
 6396            /*
 6397             *ˇ Lorem ipsum dolor sit amet,
 6398             * consectetur adipiscing elit.
 6399             */
 6400            /*
 6401             *ˇ Lorem ipsum dolor sit amet,
 6402             * consectetur adipiscing elit.
 6403             */
 6404        "},
 6405        rust_lang.clone(),
 6406        &mut cx,
 6407    );
 6408
 6409    // multiline block comment with inline start/end delimiters
 6410    assert_rewrap(
 6411        indoc! {"
 6412            /*ˇ Lorem ipsum dolor sit amet,
 6413             * consectetur adipiscing elit. */
 6414        "},
 6415        indoc! {"
 6416            /*
 6417             *ˇ Lorem ipsum dolor sit amet,
 6418             * consectetur adipiscing elit.
 6419             */
 6420        "},
 6421        rust_lang.clone(),
 6422        &mut cx,
 6423    );
 6424
 6425    // block comment rewrap still respects paragraph bounds
 6426    assert_rewrap(
 6427        indoc! {"
 6428            /*
 6429             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6430             *
 6431             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6432             */
 6433        "},
 6434        indoc! {"
 6435            /*
 6436             *ˇ Lorem ipsum dolor sit amet,
 6437             * consectetur adipiscing elit.
 6438             *
 6439             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6440             */
 6441        "},
 6442        rust_lang.clone(),
 6443        &mut cx,
 6444    );
 6445
 6446    // documentation comments
 6447    assert_rewrap(
 6448        indoc! {"
 6449            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6450            /**
 6451             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6452             */
 6453        "},
 6454        indoc! {"
 6455            /**
 6456             *ˇ Lorem ipsum dolor sit amet,
 6457             * consectetur adipiscing elit.
 6458             */
 6459            /**
 6460             *ˇ Lorem ipsum dolor sit amet,
 6461             * consectetur adipiscing elit.
 6462             */
 6463        "},
 6464        rust_lang.clone(),
 6465        &mut cx,
 6466    );
 6467
 6468    // different, adjacent comments
 6469    assert_rewrap(
 6470        indoc! {"
 6471            /**
 6472             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6473             */
 6474            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6475            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6476        "},
 6477        indoc! {"
 6478            /**
 6479             *ˇ Lorem ipsum dolor sit amet,
 6480             * consectetur adipiscing elit.
 6481             */
 6482            /*
 6483             *ˇ Lorem ipsum dolor sit amet,
 6484             * consectetur adipiscing elit.
 6485             */
 6486            //ˇ Lorem ipsum dolor sit amet,
 6487            // consectetur adipiscing elit.
 6488        "},
 6489        rust_lang.clone(),
 6490        &mut cx,
 6491    );
 6492
 6493    // selection w/ single short block comment
 6494    assert_rewrap(
 6495        indoc! {"
 6496            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6497        "},
 6498        indoc! {"
 6499            «/*
 6500             * Lorem ipsum dolor sit amet,
 6501             * consectetur adipiscing elit.
 6502             */ˇ»
 6503        "},
 6504        rust_lang.clone(),
 6505        &mut cx,
 6506    );
 6507
 6508    // rewrapping a single comment w/ abutting comments
 6509    assert_rewrap(
 6510        indoc! {"
 6511            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6512            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6513        "},
 6514        indoc! {"
 6515            /*
 6516             * ˇLorem ipsum dolor sit amet,
 6517             * consectetur adipiscing elit.
 6518             */
 6519            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6520        "},
 6521        rust_lang.clone(),
 6522        &mut cx,
 6523    );
 6524
 6525    // selection w/ non-abutting short block comments
 6526    assert_rewrap(
 6527        indoc! {"
 6528            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6529
 6530            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6531        "},
 6532        indoc! {"
 6533            «/*
 6534             * Lorem ipsum dolor sit amet,
 6535             * consectetur adipiscing elit.
 6536             */
 6537
 6538            /*
 6539             * Lorem ipsum dolor sit amet,
 6540             * consectetur adipiscing elit.
 6541             */ˇ»
 6542        "},
 6543        rust_lang.clone(),
 6544        &mut cx,
 6545    );
 6546
 6547    // selection of multiline block comments
 6548    assert_rewrap(
 6549        indoc! {"
 6550            «/* Lorem ipsum dolor sit amet,
 6551             * consectetur adipiscing elit. */ˇ»
 6552        "},
 6553        indoc! {"
 6554            «/*
 6555             * Lorem ipsum dolor sit amet,
 6556             * consectetur adipiscing elit.
 6557             */ˇ»
 6558        "},
 6559        rust_lang.clone(),
 6560        &mut cx,
 6561    );
 6562
 6563    // partial selection of multiline block comments
 6564    assert_rewrap(
 6565        indoc! {"
 6566            «/* Lorem ipsum dolor sit amet,ˇ»
 6567             * consectetur adipiscing elit. */
 6568            /* Lorem ipsum dolor sit amet,
 6569             «* consectetur adipiscing elit. */ˇ»
 6570        "},
 6571        indoc! {"
 6572            «/*
 6573             * Lorem ipsum dolor sit amet,ˇ»
 6574             * consectetur adipiscing elit. */
 6575            /* Lorem ipsum dolor sit amet,
 6576             «* consectetur adipiscing elit.
 6577             */ˇ»
 6578        "},
 6579        rust_lang.clone(),
 6580        &mut cx,
 6581    );
 6582
 6583    // selection w/ abutting short block comments
 6584    // TODO: should not be combined; should rewrap as 2 comments
 6585    assert_rewrap(
 6586        indoc! {"
 6587            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6588            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6589        "},
 6590        // desired behavior:
 6591        // indoc! {"
 6592        //     «/*
 6593        //      * Lorem ipsum dolor sit amet,
 6594        //      * consectetur adipiscing elit.
 6595        //      */
 6596        //     /*
 6597        //      * Lorem ipsum dolor sit amet,
 6598        //      * consectetur adipiscing elit.
 6599        //      */ˇ»
 6600        // "},
 6601        // actual behaviour:
 6602        indoc! {"
 6603            «/*
 6604             * Lorem ipsum dolor sit amet,
 6605             * consectetur adipiscing elit. Lorem
 6606             * ipsum dolor sit amet, consectetur
 6607             * adipiscing elit.
 6608             */ˇ»
 6609        "},
 6610        rust_lang.clone(),
 6611        &mut cx,
 6612    );
 6613
 6614    // TODO: same as above, but with delimiters on separate line
 6615    // assert_rewrap(
 6616    //     indoc! {"
 6617    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6618    //          */
 6619    //         /*
 6620    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6621    //     "},
 6622    //     // desired:
 6623    //     // indoc! {"
 6624    //     //     «/*
 6625    //     //      * Lorem ipsum dolor sit amet,
 6626    //     //      * consectetur adipiscing elit.
 6627    //     //      */
 6628    //     //     /*
 6629    //     //      * Lorem ipsum dolor sit amet,
 6630    //     //      * consectetur adipiscing elit.
 6631    //     //      */ˇ»
 6632    //     // "},
 6633    //     // actual: (but with trailing w/s on the empty lines)
 6634    //     indoc! {"
 6635    //         «/*
 6636    //          * Lorem ipsum dolor sit amet,
 6637    //          * consectetur adipiscing elit.
 6638    //          *
 6639    //          */
 6640    //         /*
 6641    //          *
 6642    //          * Lorem ipsum dolor sit amet,
 6643    //          * consectetur adipiscing elit.
 6644    //          */ˇ»
 6645    //     "},
 6646    //     rust_lang.clone(),
 6647    //     &mut cx,
 6648    // );
 6649
 6650    // TODO these are unhandled edge cases; not correct, just documenting known issues
 6651    assert_rewrap(
 6652        indoc! {"
 6653            /*
 6654             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6655             */
 6656            /*
 6657             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6658            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 6659        "},
 6660        // desired:
 6661        // indoc! {"
 6662        //     /*
 6663        //      *ˇ Lorem ipsum dolor sit amet,
 6664        //      * consectetur adipiscing elit.
 6665        //      */
 6666        //     /*
 6667        //      *ˇ Lorem ipsum dolor sit amet,
 6668        //      * consectetur adipiscing elit.
 6669        //      */
 6670        //     /*
 6671        //      *ˇ Lorem ipsum dolor sit amet
 6672        //      */ /* consectetur adipiscing elit. */
 6673        // "},
 6674        // actual:
 6675        indoc! {"
 6676            /*
 6677             //ˇ Lorem ipsum dolor sit amet,
 6678             // consectetur adipiscing elit.
 6679             */
 6680            /*
 6681             * //ˇ Lorem ipsum dolor sit amet,
 6682             * consectetur adipiscing elit.
 6683             */
 6684            /*
 6685             *ˇ Lorem ipsum dolor sit amet */ /*
 6686             * consectetur adipiscing elit.
 6687             */
 6688        "},
 6689        rust_lang,
 6690        &mut cx,
 6691    );
 6692
 6693    #[track_caller]
 6694    fn assert_rewrap(
 6695        unwrapped_text: &str,
 6696        wrapped_text: &str,
 6697        language: Arc<Language>,
 6698        cx: &mut EditorTestContext,
 6699    ) {
 6700        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6701        cx.set_state(unwrapped_text);
 6702        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6703        cx.assert_editor_state(wrapped_text);
 6704    }
 6705}
 6706
 6707#[gpui::test]
 6708async fn test_hard_wrap(cx: &mut TestAppContext) {
 6709    init_test(cx, |_| {});
 6710    let mut cx = EditorTestContext::new(cx).await;
 6711
 6712    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 6713    cx.update_editor(|editor, _, cx| {
 6714        editor.set_hard_wrap(Some(14), cx);
 6715    });
 6716
 6717    cx.set_state(indoc!(
 6718        "
 6719        one two three ˇ
 6720        "
 6721    ));
 6722    cx.simulate_input("four");
 6723    cx.run_until_parked();
 6724
 6725    cx.assert_editor_state(indoc!(
 6726        "
 6727        one two three
 6728        fourˇ
 6729        "
 6730    ));
 6731
 6732    cx.update_editor(|editor, window, cx| {
 6733        editor.newline(&Default::default(), window, cx);
 6734    });
 6735    cx.run_until_parked();
 6736    cx.assert_editor_state(indoc!(
 6737        "
 6738        one two three
 6739        four
 6740        ˇ
 6741        "
 6742    ));
 6743
 6744    cx.simulate_input("five");
 6745    cx.run_until_parked();
 6746    cx.assert_editor_state(indoc!(
 6747        "
 6748        one two three
 6749        four
 6750        fiveˇ
 6751        "
 6752    ));
 6753
 6754    cx.update_editor(|editor, window, cx| {
 6755        editor.newline(&Default::default(), window, cx);
 6756    });
 6757    cx.run_until_parked();
 6758    cx.simulate_input("# ");
 6759    cx.run_until_parked();
 6760    cx.assert_editor_state(indoc!(
 6761        "
 6762        one two three
 6763        four
 6764        five
 6765        # ˇ
 6766        "
 6767    ));
 6768
 6769    cx.update_editor(|editor, window, cx| {
 6770        editor.newline(&Default::default(), window, cx);
 6771    });
 6772    cx.run_until_parked();
 6773    cx.assert_editor_state(indoc!(
 6774        "
 6775        one two three
 6776        four
 6777        five
 6778        #\x20
 6779 6780        "
 6781    ));
 6782
 6783    cx.simulate_input(" 6");
 6784    cx.run_until_parked();
 6785    cx.assert_editor_state(indoc!(
 6786        "
 6787        one two three
 6788        four
 6789        five
 6790        #
 6791        # 6ˇ
 6792        "
 6793    ));
 6794}
 6795
 6796#[gpui::test]
 6797async fn test_cut_line_ends(cx: &mut TestAppContext) {
 6798    init_test(cx, |_| {});
 6799
 6800    let mut cx = EditorTestContext::new(cx).await;
 6801
 6802    cx.set_state(indoc! {"The quick brownˇ"});
 6803    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 6804    cx.assert_editor_state(indoc! {"The quick brownˇ"});
 6805
 6806    cx.set_state(indoc! {"The emacs foxˇ"});
 6807    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 6808    cx.assert_editor_state(indoc! {"The emacs foxˇ"});
 6809
 6810    cx.set_state(indoc! {"
 6811        The quick« brownˇ»
 6812        fox jumps overˇ
 6813        the lazy dog"});
 6814    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6815    cx.assert_editor_state(indoc! {"
 6816        The quickˇ
 6817        ˇthe lazy dog"});
 6818
 6819    cx.set_state(indoc! {"
 6820        The quick« brownˇ»
 6821        fox jumps overˇ
 6822        the lazy dog"});
 6823    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 6824    cx.assert_editor_state(indoc! {"
 6825        The quickˇ
 6826        fox jumps overˇthe lazy dog"});
 6827
 6828    cx.set_state(indoc! {"
 6829        The quick« brownˇ»
 6830        fox jumps overˇ
 6831        the lazy dog"});
 6832    cx.update_editor(|e, window, cx| {
 6833        e.cut_to_end_of_line(
 6834            &CutToEndOfLine {
 6835                stop_at_newlines: true,
 6836            },
 6837            window,
 6838            cx,
 6839        )
 6840    });
 6841    cx.assert_editor_state(indoc! {"
 6842        The quickˇ
 6843        fox jumps overˇ
 6844        the lazy dog"});
 6845
 6846    cx.set_state(indoc! {"
 6847        The quick« brownˇ»
 6848        fox jumps overˇ
 6849        the lazy dog"});
 6850    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 6851    cx.assert_editor_state(indoc! {"
 6852        The quickˇ
 6853        fox jumps overˇthe lazy dog"});
 6854}
 6855
 6856#[gpui::test]
 6857async fn test_clipboard(cx: &mut TestAppContext) {
 6858    init_test(cx, |_| {});
 6859
 6860    let mut cx = EditorTestContext::new(cx).await;
 6861
 6862    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 6863    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6864    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 6865
 6866    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 6867    cx.set_state("two ˇfour ˇsix ˇ");
 6868    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6869    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 6870
 6871    // Paste again but with only two cursors. Since the number of cursors doesn't
 6872    // match the number of slices in the clipboard, the entire clipboard text
 6873    // is pasted at each cursor.
 6874    cx.set_state("ˇtwo one✅ four three six five ˇ");
 6875    cx.update_editor(|e, window, cx| {
 6876        e.handle_input("( ", window, cx);
 6877        e.paste(&Paste, window, cx);
 6878        e.handle_input(") ", window, cx);
 6879    });
 6880    cx.assert_editor_state(
 6881        &([
 6882            "( one✅ ",
 6883            "three ",
 6884            "five ) ˇtwo one✅ four three six five ( one✅ ",
 6885            "three ",
 6886            "five ) ˇ",
 6887        ]
 6888        .join("\n")),
 6889    );
 6890
 6891    // Cut with three selections, one of which is full-line.
 6892    cx.set_state(indoc! {"
 6893        1«2ˇ»3
 6894        4ˇ567
 6895        «8ˇ»9"});
 6896    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6897    cx.assert_editor_state(indoc! {"
 6898        1ˇ3
 6899        ˇ9"});
 6900
 6901    // Paste with three selections, noticing how the copied selection that was full-line
 6902    // gets inserted before the second cursor.
 6903    cx.set_state(indoc! {"
 6904        1ˇ3
 6905 6906        «oˇ»ne"});
 6907    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6908    cx.assert_editor_state(indoc! {"
 6909        12ˇ3
 6910        4567
 6911 6912        8ˇne"});
 6913
 6914    // Copy with a single cursor only, which writes the whole line into the clipboard.
 6915    cx.set_state(indoc! {"
 6916        The quick brown
 6917        fox juˇmps over
 6918        the lazy dog"});
 6919    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6920    assert_eq!(
 6921        cx.read_from_clipboard()
 6922            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6923        Some("fox jumps over\n".to_string())
 6924    );
 6925
 6926    // Paste with three selections, noticing how the copied full-line selection is inserted
 6927    // before the empty selections but replaces the selection that is non-empty.
 6928    cx.set_state(indoc! {"
 6929        Tˇhe quick brown
 6930        «foˇ»x jumps over
 6931        tˇhe lazy dog"});
 6932    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6933    cx.assert_editor_state(indoc! {"
 6934        fox jumps over
 6935        Tˇhe quick brown
 6936        fox jumps over
 6937        ˇx jumps over
 6938        fox jumps over
 6939        tˇhe lazy dog"});
 6940}
 6941
 6942#[gpui::test]
 6943async fn test_copy_trim(cx: &mut TestAppContext) {
 6944    init_test(cx, |_| {});
 6945
 6946    let mut cx = EditorTestContext::new(cx).await;
 6947    cx.set_state(
 6948        r#"            «for selection in selections.iter() {
 6949            let mut start = selection.start;
 6950            let mut end = selection.end;
 6951            let is_entire_line = selection.is_empty();
 6952            if is_entire_line {
 6953                start = Point::new(start.row, 0);ˇ»
 6954                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6955            }
 6956        "#,
 6957    );
 6958    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6959    assert_eq!(
 6960        cx.read_from_clipboard()
 6961            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6962        Some(
 6963            "for selection in selections.iter() {
 6964            let mut start = selection.start;
 6965            let mut end = selection.end;
 6966            let is_entire_line = selection.is_empty();
 6967            if is_entire_line {
 6968                start = Point::new(start.row, 0);"
 6969                .to_string()
 6970        ),
 6971        "Regular copying preserves all indentation selected",
 6972    );
 6973    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6974    assert_eq!(
 6975        cx.read_from_clipboard()
 6976            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6977        Some(
 6978            "for selection in selections.iter() {
 6979let mut start = selection.start;
 6980let mut end = selection.end;
 6981let is_entire_line = selection.is_empty();
 6982if is_entire_line {
 6983    start = Point::new(start.row, 0);"
 6984                .to_string()
 6985        ),
 6986        "Copying with stripping should strip all leading whitespaces"
 6987    );
 6988
 6989    cx.set_state(
 6990        r#"       «     for selection in selections.iter() {
 6991            let mut start = selection.start;
 6992            let mut end = selection.end;
 6993            let is_entire_line = selection.is_empty();
 6994            if is_entire_line {
 6995                start = Point::new(start.row, 0);ˇ»
 6996                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6997            }
 6998        "#,
 6999    );
 7000    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7001    assert_eq!(
 7002        cx.read_from_clipboard()
 7003            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7004        Some(
 7005            "     for selection in selections.iter() {
 7006            let mut start = selection.start;
 7007            let mut end = selection.end;
 7008            let is_entire_line = selection.is_empty();
 7009            if is_entire_line {
 7010                start = Point::new(start.row, 0);"
 7011                .to_string()
 7012        ),
 7013        "Regular copying preserves all indentation selected",
 7014    );
 7015    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7016    assert_eq!(
 7017        cx.read_from_clipboard()
 7018            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7019        Some(
 7020            "for selection in selections.iter() {
 7021let mut start = selection.start;
 7022let mut end = selection.end;
 7023let is_entire_line = selection.is_empty();
 7024if is_entire_line {
 7025    start = Point::new(start.row, 0);"
 7026                .to_string()
 7027        ),
 7028        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 7029    );
 7030
 7031    cx.set_state(
 7032        r#"       «ˇ     for selection in selections.iter() {
 7033            let mut start = selection.start;
 7034            let mut end = selection.end;
 7035            let is_entire_line = selection.is_empty();
 7036            if is_entire_line {
 7037                start = Point::new(start.row, 0);»
 7038                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7039            }
 7040        "#,
 7041    );
 7042    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7043    assert_eq!(
 7044        cx.read_from_clipboard()
 7045            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7046        Some(
 7047            "     for selection in selections.iter() {
 7048            let mut start = selection.start;
 7049            let mut end = selection.end;
 7050            let is_entire_line = selection.is_empty();
 7051            if is_entire_line {
 7052                start = Point::new(start.row, 0);"
 7053                .to_string()
 7054        ),
 7055        "Regular copying for reverse selection works the same",
 7056    );
 7057    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7058    assert_eq!(
 7059        cx.read_from_clipboard()
 7060            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7061        Some(
 7062            "for selection in selections.iter() {
 7063let mut start = selection.start;
 7064let mut end = selection.end;
 7065let is_entire_line = selection.is_empty();
 7066if is_entire_line {
 7067    start = Point::new(start.row, 0);"
 7068                .to_string()
 7069        ),
 7070        "Copying with stripping for reverse selection works the same"
 7071    );
 7072
 7073    cx.set_state(
 7074        r#"            for selection «in selections.iter() {
 7075            let mut start = selection.start;
 7076            let mut end = selection.end;
 7077            let is_entire_line = selection.is_empty();
 7078            if is_entire_line {
 7079                start = Point::new(start.row, 0);ˇ»
 7080                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7081            }
 7082        "#,
 7083    );
 7084    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7085    assert_eq!(
 7086        cx.read_from_clipboard()
 7087            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7088        Some(
 7089            "in selections.iter() {
 7090            let mut start = selection.start;
 7091            let mut end = selection.end;
 7092            let is_entire_line = selection.is_empty();
 7093            if is_entire_line {
 7094                start = Point::new(start.row, 0);"
 7095                .to_string()
 7096        ),
 7097        "When selecting past the indent, the copying works as usual",
 7098    );
 7099    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7100    assert_eq!(
 7101        cx.read_from_clipboard()
 7102            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7103        Some(
 7104            "in selections.iter() {
 7105            let mut start = selection.start;
 7106            let mut end = selection.end;
 7107            let is_entire_line = selection.is_empty();
 7108            if is_entire_line {
 7109                start = Point::new(start.row, 0);"
 7110                .to_string()
 7111        ),
 7112        "When selecting past the indent, nothing is trimmed"
 7113    );
 7114
 7115    cx.set_state(
 7116        r#"            «for selection in selections.iter() {
 7117            let mut start = selection.start;
 7118
 7119            let mut end = selection.end;
 7120            let is_entire_line = selection.is_empty();
 7121            if is_entire_line {
 7122                start = Point::new(start.row, 0);
 7123ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7124            }
 7125        "#,
 7126    );
 7127    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7128    assert_eq!(
 7129        cx.read_from_clipboard()
 7130            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7131        Some(
 7132            "for selection in selections.iter() {
 7133let mut start = selection.start;
 7134
 7135let mut end = selection.end;
 7136let is_entire_line = selection.is_empty();
 7137if is_entire_line {
 7138    start = Point::new(start.row, 0);
 7139"
 7140            .to_string()
 7141        ),
 7142        "Copying with stripping should ignore empty lines"
 7143    );
 7144}
 7145
 7146#[gpui::test]
 7147async fn test_paste_multiline(cx: &mut TestAppContext) {
 7148    init_test(cx, |_| {});
 7149
 7150    let mut cx = EditorTestContext::new(cx).await;
 7151    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7152
 7153    // Cut an indented block, without the leading whitespace.
 7154    cx.set_state(indoc! {"
 7155        const a: B = (
 7156            c(),
 7157            «d(
 7158                e,
 7159                f
 7160            )ˇ»
 7161        );
 7162    "});
 7163    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7164    cx.assert_editor_state(indoc! {"
 7165        const a: B = (
 7166            c(),
 7167            ˇ
 7168        );
 7169    "});
 7170
 7171    // Paste it at the same position.
 7172    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7173    cx.assert_editor_state(indoc! {"
 7174        const a: B = (
 7175            c(),
 7176            d(
 7177                e,
 7178                f
 7179 7180        );
 7181    "});
 7182
 7183    // Paste it at a line with a lower indent level.
 7184    cx.set_state(indoc! {"
 7185        ˇ
 7186        const a: B = (
 7187            c(),
 7188        );
 7189    "});
 7190    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7191    cx.assert_editor_state(indoc! {"
 7192        d(
 7193            e,
 7194            f
 7195 7196        const a: B = (
 7197            c(),
 7198        );
 7199    "});
 7200
 7201    // Cut an indented block, with the leading whitespace.
 7202    cx.set_state(indoc! {"
 7203        const a: B = (
 7204            c(),
 7205        «    d(
 7206                e,
 7207                f
 7208            )
 7209        ˇ»);
 7210    "});
 7211    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7212    cx.assert_editor_state(indoc! {"
 7213        const a: B = (
 7214            c(),
 7215        ˇ);
 7216    "});
 7217
 7218    // Paste it at the same position.
 7219    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7220    cx.assert_editor_state(indoc! {"
 7221        const a: B = (
 7222            c(),
 7223            d(
 7224                e,
 7225                f
 7226            )
 7227        ˇ);
 7228    "});
 7229
 7230    // Paste it at a line with a higher indent level.
 7231    cx.set_state(indoc! {"
 7232        const a: B = (
 7233            c(),
 7234            d(
 7235                e,
 7236 7237            )
 7238        );
 7239    "});
 7240    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7241    cx.assert_editor_state(indoc! {"
 7242        const a: B = (
 7243            c(),
 7244            d(
 7245                e,
 7246                f    d(
 7247                    e,
 7248                    f
 7249                )
 7250        ˇ
 7251            )
 7252        );
 7253    "});
 7254
 7255    // Copy an indented block, starting mid-line
 7256    cx.set_state(indoc! {"
 7257        const a: B = (
 7258            c(),
 7259            somethin«g(
 7260                e,
 7261                f
 7262            )ˇ»
 7263        );
 7264    "});
 7265    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7266
 7267    // Paste it on a line with a lower indent level
 7268    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7269    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7270    cx.assert_editor_state(indoc! {"
 7271        const a: B = (
 7272            c(),
 7273            something(
 7274                e,
 7275                f
 7276            )
 7277        );
 7278        g(
 7279            e,
 7280            f
 7281"});
 7282}
 7283
 7284#[gpui::test]
 7285async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7286    init_test(cx, |_| {});
 7287
 7288    cx.write_to_clipboard(ClipboardItem::new_string(
 7289        "    d(\n        e\n    );\n".into(),
 7290    ));
 7291
 7292    let mut cx = EditorTestContext::new(cx).await;
 7293    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7294
 7295    cx.set_state(indoc! {"
 7296        fn a() {
 7297            b();
 7298            if c() {
 7299                ˇ
 7300            }
 7301        }
 7302    "});
 7303
 7304    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7305    cx.assert_editor_state(indoc! {"
 7306        fn a() {
 7307            b();
 7308            if c() {
 7309                d(
 7310                    e
 7311                );
 7312        ˇ
 7313            }
 7314        }
 7315    "});
 7316
 7317    cx.set_state(indoc! {"
 7318        fn a() {
 7319            b();
 7320            ˇ
 7321        }
 7322    "});
 7323
 7324    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7325    cx.assert_editor_state(indoc! {"
 7326        fn a() {
 7327            b();
 7328            d(
 7329                e
 7330            );
 7331        ˇ
 7332        }
 7333    "});
 7334}
 7335
 7336#[gpui::test]
 7337fn test_select_all(cx: &mut TestAppContext) {
 7338    init_test(cx, |_| {});
 7339
 7340    let editor = cx.add_window(|window, cx| {
 7341        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7342        build_editor(buffer, window, cx)
 7343    });
 7344    _ = editor.update(cx, |editor, window, cx| {
 7345        editor.select_all(&SelectAll, window, cx);
 7346        assert_eq!(
 7347            editor.selections.display_ranges(cx),
 7348            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7349        );
 7350    });
 7351}
 7352
 7353#[gpui::test]
 7354fn test_select_line(cx: &mut TestAppContext) {
 7355    init_test(cx, |_| {});
 7356
 7357    let editor = cx.add_window(|window, cx| {
 7358        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7359        build_editor(buffer, window, cx)
 7360    });
 7361    _ = editor.update(cx, |editor, window, cx| {
 7362        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7363            s.select_display_ranges([
 7364                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7365                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7366                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7367                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7368            ])
 7369        });
 7370        editor.select_line(&SelectLine, window, cx);
 7371        assert_eq!(
 7372            editor.selections.display_ranges(cx),
 7373            vec![
 7374                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7375                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7376            ]
 7377        );
 7378    });
 7379
 7380    _ = editor.update(cx, |editor, window, cx| {
 7381        editor.select_line(&SelectLine, window, cx);
 7382        assert_eq!(
 7383            editor.selections.display_ranges(cx),
 7384            vec![
 7385                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7386                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7387            ]
 7388        );
 7389    });
 7390
 7391    _ = editor.update(cx, |editor, window, cx| {
 7392        editor.select_line(&SelectLine, window, cx);
 7393        assert_eq!(
 7394            editor.selections.display_ranges(cx),
 7395            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 7396        );
 7397    });
 7398}
 7399
 7400#[gpui::test]
 7401async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7402    init_test(cx, |_| {});
 7403    let mut cx = EditorTestContext::new(cx).await;
 7404
 7405    #[track_caller]
 7406    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7407        cx.set_state(initial_state);
 7408        cx.update_editor(|e, window, cx| {
 7409            e.split_selection_into_lines(&Default::default(), window, cx)
 7410        });
 7411        cx.assert_editor_state(expected_state);
 7412    }
 7413
 7414    // Selection starts and ends at the middle of lines, left-to-right
 7415    test(
 7416        &mut cx,
 7417        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7418        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7419    );
 7420    // Same thing, right-to-left
 7421    test(
 7422        &mut cx,
 7423        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7424        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7425    );
 7426
 7427    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7428    test(
 7429        &mut cx,
 7430        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7431        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7432    );
 7433    // Same thing, right-to-left
 7434    test(
 7435        &mut cx,
 7436        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7437        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7438    );
 7439
 7440    // Whole buffer, left-to-right, last line ends with newline
 7441    test(
 7442        &mut cx,
 7443        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7444        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7445    );
 7446    // Same thing, right-to-left
 7447    test(
 7448        &mut cx,
 7449        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 7450        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7451    );
 7452
 7453    // Starts at the end of a line, ends at the start of another
 7454    test(
 7455        &mut cx,
 7456        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 7457        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 7458    );
 7459}
 7460
 7461#[gpui::test]
 7462async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 7463    init_test(cx, |_| {});
 7464
 7465    let editor = cx.add_window(|window, cx| {
 7466        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 7467        build_editor(buffer, window, cx)
 7468    });
 7469
 7470    // setup
 7471    _ = editor.update(cx, |editor, window, cx| {
 7472        editor.fold_creases(
 7473            vec![
 7474                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7475                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7476                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7477            ],
 7478            true,
 7479            window,
 7480            cx,
 7481        );
 7482        assert_eq!(
 7483            editor.display_text(cx),
 7484            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7485        );
 7486    });
 7487
 7488    _ = editor.update(cx, |editor, window, cx| {
 7489        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7490            s.select_display_ranges([
 7491                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7492                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7493                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7494                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7495            ])
 7496        });
 7497        editor.split_selection_into_lines(&Default::default(), window, cx);
 7498        assert_eq!(
 7499            editor.display_text(cx),
 7500            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7501        );
 7502    });
 7503    EditorTestContext::for_editor(editor, cx)
 7504        .await
 7505        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7506
 7507    _ = editor.update(cx, |editor, window, cx| {
 7508        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7509            s.select_display_ranges([
 7510                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7511            ])
 7512        });
 7513        editor.split_selection_into_lines(&Default::default(), window, cx);
 7514        assert_eq!(
 7515            editor.display_text(cx),
 7516            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7517        );
 7518        assert_eq!(
 7519            editor.selections.display_ranges(cx),
 7520            [
 7521                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7522                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7523                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7524                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7525                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7526                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7527                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7528            ]
 7529        );
 7530    });
 7531    EditorTestContext::for_editor(editor, cx)
 7532        .await
 7533        .assert_editor_state(
 7534            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7535        );
 7536}
 7537
 7538#[gpui::test]
 7539async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7540    init_test(cx, |_| {});
 7541
 7542    let mut cx = EditorTestContext::new(cx).await;
 7543
 7544    cx.set_state(indoc!(
 7545        r#"abc
 7546           defˇghi
 7547
 7548           jk
 7549           nlmo
 7550           "#
 7551    ));
 7552
 7553    cx.update_editor(|editor, window, cx| {
 7554        editor.add_selection_above(&Default::default(), window, cx);
 7555    });
 7556
 7557    cx.assert_editor_state(indoc!(
 7558        r#"abcˇ
 7559           defˇghi
 7560
 7561           jk
 7562           nlmo
 7563           "#
 7564    ));
 7565
 7566    cx.update_editor(|editor, window, cx| {
 7567        editor.add_selection_above(&Default::default(), window, cx);
 7568    });
 7569
 7570    cx.assert_editor_state(indoc!(
 7571        r#"abcˇ
 7572            defˇghi
 7573
 7574            jk
 7575            nlmo
 7576            "#
 7577    ));
 7578
 7579    cx.update_editor(|editor, window, cx| {
 7580        editor.add_selection_below(&Default::default(), window, cx);
 7581    });
 7582
 7583    cx.assert_editor_state(indoc!(
 7584        r#"abc
 7585           defˇghi
 7586
 7587           jk
 7588           nlmo
 7589           "#
 7590    ));
 7591
 7592    cx.update_editor(|editor, window, cx| {
 7593        editor.undo_selection(&Default::default(), window, cx);
 7594    });
 7595
 7596    cx.assert_editor_state(indoc!(
 7597        r#"abcˇ
 7598           defˇghi
 7599
 7600           jk
 7601           nlmo
 7602           "#
 7603    ));
 7604
 7605    cx.update_editor(|editor, window, cx| {
 7606        editor.redo_selection(&Default::default(), window, cx);
 7607    });
 7608
 7609    cx.assert_editor_state(indoc!(
 7610        r#"abc
 7611           defˇghi
 7612
 7613           jk
 7614           nlmo
 7615           "#
 7616    ));
 7617
 7618    cx.update_editor(|editor, window, cx| {
 7619        editor.add_selection_below(&Default::default(), window, cx);
 7620    });
 7621
 7622    cx.assert_editor_state(indoc!(
 7623        r#"abc
 7624           defˇghi
 7625           ˇ
 7626           jk
 7627           nlmo
 7628           "#
 7629    ));
 7630
 7631    cx.update_editor(|editor, window, cx| {
 7632        editor.add_selection_below(&Default::default(), window, cx);
 7633    });
 7634
 7635    cx.assert_editor_state(indoc!(
 7636        r#"abc
 7637           defˇghi
 7638           ˇ
 7639           jkˇ
 7640           nlmo
 7641           "#
 7642    ));
 7643
 7644    cx.update_editor(|editor, window, cx| {
 7645        editor.add_selection_below(&Default::default(), window, cx);
 7646    });
 7647
 7648    cx.assert_editor_state(indoc!(
 7649        r#"abc
 7650           defˇghi
 7651           ˇ
 7652           jkˇ
 7653           nlmˇo
 7654           "#
 7655    ));
 7656
 7657    cx.update_editor(|editor, window, cx| {
 7658        editor.add_selection_below(&Default::default(), window, cx);
 7659    });
 7660
 7661    cx.assert_editor_state(indoc!(
 7662        r#"abc
 7663           defˇghi
 7664           ˇ
 7665           jkˇ
 7666           nlmˇo
 7667           ˇ"#
 7668    ));
 7669
 7670    // change selections
 7671    cx.set_state(indoc!(
 7672        r#"abc
 7673           def«ˇg»hi
 7674
 7675           jk
 7676           nlmo
 7677           "#
 7678    ));
 7679
 7680    cx.update_editor(|editor, window, cx| {
 7681        editor.add_selection_below(&Default::default(), window, cx);
 7682    });
 7683
 7684    cx.assert_editor_state(indoc!(
 7685        r#"abc
 7686           def«ˇg»hi
 7687
 7688           jk
 7689           nlm«ˇo»
 7690           "#
 7691    ));
 7692
 7693    cx.update_editor(|editor, window, cx| {
 7694        editor.add_selection_below(&Default::default(), window, cx);
 7695    });
 7696
 7697    cx.assert_editor_state(indoc!(
 7698        r#"abc
 7699           def«ˇg»hi
 7700
 7701           jk
 7702           nlm«ˇo»
 7703           "#
 7704    ));
 7705
 7706    cx.update_editor(|editor, window, cx| {
 7707        editor.add_selection_above(&Default::default(), window, cx);
 7708    });
 7709
 7710    cx.assert_editor_state(indoc!(
 7711        r#"abc
 7712           def«ˇg»hi
 7713
 7714           jk
 7715           nlmo
 7716           "#
 7717    ));
 7718
 7719    cx.update_editor(|editor, window, cx| {
 7720        editor.add_selection_above(&Default::default(), window, cx);
 7721    });
 7722
 7723    cx.assert_editor_state(indoc!(
 7724        r#"abc
 7725           def«ˇg»hi
 7726
 7727           jk
 7728           nlmo
 7729           "#
 7730    ));
 7731
 7732    // Change selections again
 7733    cx.set_state(indoc!(
 7734        r#"a«bc
 7735           defgˇ»hi
 7736
 7737           jk
 7738           nlmo
 7739           "#
 7740    ));
 7741
 7742    cx.update_editor(|editor, window, cx| {
 7743        editor.add_selection_below(&Default::default(), window, cx);
 7744    });
 7745
 7746    cx.assert_editor_state(indoc!(
 7747        r#"a«bcˇ»
 7748           d«efgˇ»hi
 7749
 7750           j«kˇ»
 7751           nlmo
 7752           "#
 7753    ));
 7754
 7755    cx.update_editor(|editor, window, cx| {
 7756        editor.add_selection_below(&Default::default(), window, cx);
 7757    });
 7758    cx.assert_editor_state(indoc!(
 7759        r#"a«bcˇ»
 7760           d«efgˇ»hi
 7761
 7762           j«kˇ»
 7763           n«lmoˇ»
 7764           "#
 7765    ));
 7766    cx.update_editor(|editor, window, cx| {
 7767        editor.add_selection_above(&Default::default(), window, cx);
 7768    });
 7769
 7770    cx.assert_editor_state(indoc!(
 7771        r#"a«bcˇ»
 7772           d«efgˇ»hi
 7773
 7774           j«kˇ»
 7775           nlmo
 7776           "#
 7777    ));
 7778
 7779    // Change selections again
 7780    cx.set_state(indoc!(
 7781        r#"abc
 7782           d«ˇefghi
 7783
 7784           jk
 7785           nlm»o
 7786           "#
 7787    ));
 7788
 7789    cx.update_editor(|editor, window, cx| {
 7790        editor.add_selection_above(&Default::default(), window, cx);
 7791    });
 7792
 7793    cx.assert_editor_state(indoc!(
 7794        r#"a«ˇbc»
 7795           d«ˇef»ghi
 7796
 7797           j«ˇk»
 7798           n«ˇlm»o
 7799           "#
 7800    ));
 7801
 7802    cx.update_editor(|editor, window, cx| {
 7803        editor.add_selection_below(&Default::default(), window, cx);
 7804    });
 7805
 7806    cx.assert_editor_state(indoc!(
 7807        r#"abc
 7808           d«ˇef»ghi
 7809
 7810           j«ˇk»
 7811           n«ˇlm»o
 7812           "#
 7813    ));
 7814}
 7815
 7816#[gpui::test]
 7817async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 7818    init_test(cx, |_| {});
 7819    let mut cx = EditorTestContext::new(cx).await;
 7820
 7821    cx.set_state(indoc!(
 7822        r#"line onˇe
 7823           liˇne two
 7824           line three
 7825           line four"#
 7826    ));
 7827
 7828    cx.update_editor(|editor, window, cx| {
 7829        editor.add_selection_below(&Default::default(), window, cx);
 7830    });
 7831
 7832    // test multiple cursors expand in the same direction
 7833    cx.assert_editor_state(indoc!(
 7834        r#"line onˇe
 7835           liˇne twˇo
 7836           liˇne three
 7837           line four"#
 7838    ));
 7839
 7840    cx.update_editor(|editor, window, cx| {
 7841        editor.add_selection_below(&Default::default(), window, cx);
 7842    });
 7843
 7844    cx.update_editor(|editor, window, cx| {
 7845        editor.add_selection_below(&Default::default(), window, cx);
 7846    });
 7847
 7848    // test multiple cursors expand below overflow
 7849    cx.assert_editor_state(indoc!(
 7850        r#"line onˇe
 7851           liˇne twˇo
 7852           liˇne thˇree
 7853           liˇne foˇur"#
 7854    ));
 7855
 7856    cx.update_editor(|editor, window, cx| {
 7857        editor.add_selection_above(&Default::default(), window, cx);
 7858    });
 7859
 7860    // test multiple cursors retrieves back correctly
 7861    cx.assert_editor_state(indoc!(
 7862        r#"line onˇe
 7863           liˇne twˇo
 7864           liˇne thˇree
 7865           line four"#
 7866    ));
 7867
 7868    cx.update_editor(|editor, window, cx| {
 7869        editor.add_selection_above(&Default::default(), window, cx);
 7870    });
 7871
 7872    cx.update_editor(|editor, window, cx| {
 7873        editor.add_selection_above(&Default::default(), window, cx);
 7874    });
 7875
 7876    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 7877    cx.assert_editor_state(indoc!(
 7878        r#"liˇne onˇe
 7879           liˇne two
 7880           line three
 7881           line four"#
 7882    ));
 7883
 7884    cx.update_editor(|editor, window, cx| {
 7885        editor.undo_selection(&Default::default(), window, cx);
 7886    });
 7887
 7888    // test undo
 7889    cx.assert_editor_state(indoc!(
 7890        r#"line onˇe
 7891           liˇne twˇo
 7892           line three
 7893           line four"#
 7894    ));
 7895
 7896    cx.update_editor(|editor, window, cx| {
 7897        editor.redo_selection(&Default::default(), window, cx);
 7898    });
 7899
 7900    // test redo
 7901    cx.assert_editor_state(indoc!(
 7902        r#"liˇne onˇe
 7903           liˇne two
 7904           line three
 7905           line four"#
 7906    ));
 7907
 7908    cx.set_state(indoc!(
 7909        r#"abcd
 7910           ef«ghˇ»
 7911           ijkl
 7912           «mˇ»nop"#
 7913    ));
 7914
 7915    cx.update_editor(|editor, window, cx| {
 7916        editor.add_selection_above(&Default::default(), window, cx);
 7917    });
 7918
 7919    // test multiple selections expand in the same direction
 7920    cx.assert_editor_state(indoc!(
 7921        r#"ab«cdˇ»
 7922           ef«ghˇ»
 7923           «iˇ»jkl
 7924           «mˇ»nop"#
 7925    ));
 7926
 7927    cx.update_editor(|editor, window, cx| {
 7928        editor.add_selection_above(&Default::default(), window, cx);
 7929    });
 7930
 7931    // test multiple selection upward overflow
 7932    cx.assert_editor_state(indoc!(
 7933        r#"ab«cdˇ»
 7934           «eˇ»f«ghˇ»
 7935           «iˇ»jkl
 7936           «mˇ»nop"#
 7937    ));
 7938
 7939    cx.update_editor(|editor, window, cx| {
 7940        editor.add_selection_below(&Default::default(), window, cx);
 7941    });
 7942
 7943    // test multiple selection retrieves back correctly
 7944    cx.assert_editor_state(indoc!(
 7945        r#"abcd
 7946           ef«ghˇ»
 7947           «iˇ»jkl
 7948           «mˇ»nop"#
 7949    ));
 7950
 7951    cx.update_editor(|editor, window, cx| {
 7952        editor.add_selection_below(&Default::default(), window, cx);
 7953    });
 7954
 7955    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 7956    cx.assert_editor_state(indoc!(
 7957        r#"abcd
 7958           ef«ghˇ»
 7959           ij«klˇ»
 7960           «mˇ»nop"#
 7961    ));
 7962
 7963    cx.update_editor(|editor, window, cx| {
 7964        editor.undo_selection(&Default::default(), window, cx);
 7965    });
 7966
 7967    // test undo
 7968    cx.assert_editor_state(indoc!(
 7969        r#"abcd
 7970           ef«ghˇ»
 7971           «iˇ»jkl
 7972           «mˇ»nop"#
 7973    ));
 7974
 7975    cx.update_editor(|editor, window, cx| {
 7976        editor.redo_selection(&Default::default(), window, cx);
 7977    });
 7978
 7979    // test redo
 7980    cx.assert_editor_state(indoc!(
 7981        r#"abcd
 7982           ef«ghˇ»
 7983           ij«klˇ»
 7984           «mˇ»nop"#
 7985    ));
 7986}
 7987
 7988#[gpui::test]
 7989async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 7990    init_test(cx, |_| {});
 7991    let mut cx = EditorTestContext::new(cx).await;
 7992
 7993    cx.set_state(indoc!(
 7994        r#"line onˇe
 7995           liˇne two
 7996           line three
 7997           line four"#
 7998    ));
 7999
 8000    cx.update_editor(|editor, window, cx| {
 8001        editor.add_selection_below(&Default::default(), window, cx);
 8002        editor.add_selection_below(&Default::default(), window, cx);
 8003        editor.add_selection_below(&Default::default(), window, cx);
 8004    });
 8005
 8006    // initial state with two multi cursor groups
 8007    cx.assert_editor_state(indoc!(
 8008        r#"line onˇe
 8009           liˇne twˇo
 8010           liˇne thˇree
 8011           liˇne foˇur"#
 8012    ));
 8013
 8014    // add single cursor in middle - simulate opt click
 8015    cx.update_editor(|editor, window, cx| {
 8016        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 8017        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8018        editor.end_selection(window, cx);
 8019    });
 8020
 8021    cx.assert_editor_state(indoc!(
 8022        r#"line onˇe
 8023           liˇne twˇo
 8024           liˇneˇ thˇree
 8025           liˇne foˇur"#
 8026    ));
 8027
 8028    cx.update_editor(|editor, window, cx| {
 8029        editor.add_selection_above(&Default::default(), window, cx);
 8030    });
 8031
 8032    // test new added selection expands above and existing selection shrinks
 8033    cx.assert_editor_state(indoc!(
 8034        r#"line onˇe
 8035           liˇneˇ twˇo
 8036           liˇneˇ thˇree
 8037           line four"#
 8038    ));
 8039
 8040    cx.update_editor(|editor, window, cx| {
 8041        editor.add_selection_above(&Default::default(), window, cx);
 8042    });
 8043
 8044    // test new added selection expands above and existing selection shrinks
 8045    cx.assert_editor_state(indoc!(
 8046        r#"lineˇ onˇe
 8047           liˇneˇ twˇo
 8048           lineˇ three
 8049           line four"#
 8050    ));
 8051
 8052    // intial state with two selection groups
 8053    cx.set_state(indoc!(
 8054        r#"abcd
 8055           ef«ghˇ»
 8056           ijkl
 8057           «mˇ»nop"#
 8058    ));
 8059
 8060    cx.update_editor(|editor, window, cx| {
 8061        editor.add_selection_above(&Default::default(), window, cx);
 8062        editor.add_selection_above(&Default::default(), window, cx);
 8063    });
 8064
 8065    cx.assert_editor_state(indoc!(
 8066        r#"ab«cdˇ»
 8067           «eˇ»f«ghˇ»
 8068           «iˇ»jkl
 8069           «mˇ»nop"#
 8070    ));
 8071
 8072    // add single selection in middle - simulate opt drag
 8073    cx.update_editor(|editor, window, cx| {
 8074        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8075        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8076        editor.update_selection(
 8077            DisplayPoint::new(DisplayRow(2), 4),
 8078            0,
 8079            gpui::Point::<f32>::default(),
 8080            window,
 8081            cx,
 8082        );
 8083        editor.end_selection(window, cx);
 8084    });
 8085
 8086    cx.assert_editor_state(indoc!(
 8087        r#"ab«cdˇ»
 8088           «eˇ»f«ghˇ»
 8089           «iˇ»jk«lˇ»
 8090           «mˇ»nop"#
 8091    ));
 8092
 8093    cx.update_editor(|editor, window, cx| {
 8094        editor.add_selection_below(&Default::default(), window, cx);
 8095    });
 8096
 8097    // test new added selection expands below, others shrinks from above
 8098    cx.assert_editor_state(indoc!(
 8099        r#"abcd
 8100           ef«ghˇ»
 8101           «iˇ»jk«lˇ»
 8102           «mˇ»no«pˇ»"#
 8103    ));
 8104}
 8105
 8106#[gpui::test]
 8107async fn test_select_next(cx: &mut TestAppContext) {
 8108    init_test(cx, |_| {});
 8109
 8110    let mut cx = EditorTestContext::new(cx).await;
 8111    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8112
 8113    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8114        .unwrap();
 8115    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8116
 8117    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8118        .unwrap();
 8119    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8120
 8121    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8122    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8123
 8124    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8125    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8126
 8127    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8128        .unwrap();
 8129    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8130
 8131    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8132        .unwrap();
 8133    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8134
 8135    // Test selection direction should be preserved
 8136    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8137
 8138    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8139        .unwrap();
 8140    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8141}
 8142
 8143#[gpui::test]
 8144async fn test_select_all_matches(cx: &mut TestAppContext) {
 8145    init_test(cx, |_| {});
 8146
 8147    let mut cx = EditorTestContext::new(cx).await;
 8148
 8149    // Test caret-only selections
 8150    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8151    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8152        .unwrap();
 8153    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8154
 8155    // Test left-to-right selections
 8156    cx.set_state("abc\n«abcˇ»\nabc");
 8157    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8158        .unwrap();
 8159    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8160
 8161    // Test right-to-left selections
 8162    cx.set_state("abc\n«ˇabc»\nabc");
 8163    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8164        .unwrap();
 8165    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8166
 8167    // Test selecting whitespace with caret selection
 8168    cx.set_state("abc\nˇ   abc\nabc");
 8169    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8170        .unwrap();
 8171    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8172
 8173    // Test selecting whitespace with left-to-right selection
 8174    cx.set_state("abc\n«ˇ  »abc\nabc");
 8175    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8176        .unwrap();
 8177    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8178
 8179    // Test no matches with right-to-left selection
 8180    cx.set_state("abc\n«  ˇ»abc\nabc");
 8181    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8182        .unwrap();
 8183    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8184
 8185    // Test with a single word and clip_at_line_ends=true (#29823)
 8186    cx.set_state("aˇbc");
 8187    cx.update_editor(|e, window, cx| {
 8188        e.set_clip_at_line_ends(true, cx);
 8189        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8190        e.set_clip_at_line_ends(false, cx);
 8191    });
 8192    cx.assert_editor_state("«abcˇ»");
 8193}
 8194
 8195#[gpui::test]
 8196async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8197    init_test(cx, |_| {});
 8198
 8199    let mut cx = EditorTestContext::new(cx).await;
 8200
 8201    let large_body_1 = "\nd".repeat(200);
 8202    let large_body_2 = "\ne".repeat(200);
 8203
 8204    cx.set_state(&format!(
 8205        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8206    ));
 8207    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8208        let scroll_position = editor.scroll_position(cx);
 8209        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8210        scroll_position
 8211    });
 8212
 8213    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8214        .unwrap();
 8215    cx.assert_editor_state(&format!(
 8216        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8217    ));
 8218    let scroll_position_after_selection =
 8219        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8220    assert_eq!(
 8221        initial_scroll_position, scroll_position_after_selection,
 8222        "Scroll position should not change after selecting all matches"
 8223    );
 8224}
 8225
 8226#[gpui::test]
 8227async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8228    init_test(cx, |_| {});
 8229
 8230    let mut cx = EditorLspTestContext::new_rust(
 8231        lsp::ServerCapabilities {
 8232            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8233            ..Default::default()
 8234        },
 8235        cx,
 8236    )
 8237    .await;
 8238
 8239    cx.set_state(indoc! {"
 8240        line 1
 8241        line 2
 8242        linˇe 3
 8243        line 4
 8244        line 5
 8245    "});
 8246
 8247    // Make an edit
 8248    cx.update_editor(|editor, window, cx| {
 8249        editor.handle_input("X", window, cx);
 8250    });
 8251
 8252    // Move cursor to a different position
 8253    cx.update_editor(|editor, window, cx| {
 8254        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8255            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8256        });
 8257    });
 8258
 8259    cx.assert_editor_state(indoc! {"
 8260        line 1
 8261        line 2
 8262        linXe 3
 8263        line 4
 8264        liˇne 5
 8265    "});
 8266
 8267    cx.lsp
 8268        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8269            Ok(Some(vec![lsp::TextEdit::new(
 8270                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8271                "PREFIX ".to_string(),
 8272            )]))
 8273        });
 8274
 8275    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8276        .unwrap()
 8277        .await
 8278        .unwrap();
 8279
 8280    cx.assert_editor_state(indoc! {"
 8281        PREFIX line 1
 8282        line 2
 8283        linXe 3
 8284        line 4
 8285        liˇne 5
 8286    "});
 8287
 8288    // Undo formatting
 8289    cx.update_editor(|editor, window, cx| {
 8290        editor.undo(&Default::default(), window, cx);
 8291    });
 8292
 8293    // Verify cursor moved back to position after edit
 8294    cx.assert_editor_state(indoc! {"
 8295        line 1
 8296        line 2
 8297        linXˇe 3
 8298        line 4
 8299        line 5
 8300    "});
 8301}
 8302
 8303#[gpui::test]
 8304async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8305    init_test(cx, |_| {});
 8306
 8307    let mut cx = EditorTestContext::new(cx).await;
 8308
 8309    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 8310    cx.update_editor(|editor, window, cx| {
 8311        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8312    });
 8313
 8314    cx.set_state(indoc! {"
 8315        line 1
 8316        line 2
 8317        linˇe 3
 8318        line 4
 8319        line 5
 8320        line 6
 8321        line 7
 8322        line 8
 8323        line 9
 8324        line 10
 8325    "});
 8326
 8327    let snapshot = cx.buffer_snapshot();
 8328    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8329
 8330    cx.update(|_, cx| {
 8331        provider.update(cx, |provider, _| {
 8332            provider.set_edit_prediction(Some(edit_prediction::EditPrediction::Local {
 8333                id: None,
 8334                edits: vec![(edit_position..edit_position, "X".into())],
 8335                edit_preview: None,
 8336            }))
 8337        })
 8338    });
 8339
 8340    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8341    cx.update_editor(|editor, window, cx| {
 8342        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8343    });
 8344
 8345    cx.assert_editor_state(indoc! {"
 8346        line 1
 8347        line 2
 8348        lineXˇ 3
 8349        line 4
 8350        line 5
 8351        line 6
 8352        line 7
 8353        line 8
 8354        line 9
 8355        line 10
 8356    "});
 8357
 8358    cx.update_editor(|editor, window, cx| {
 8359        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8360            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8361        });
 8362    });
 8363
 8364    cx.assert_editor_state(indoc! {"
 8365        line 1
 8366        line 2
 8367        lineX 3
 8368        line 4
 8369        line 5
 8370        line 6
 8371        line 7
 8372        line 8
 8373        line 9
 8374        liˇne 10
 8375    "});
 8376
 8377    cx.update_editor(|editor, window, cx| {
 8378        editor.undo(&Default::default(), window, cx);
 8379    });
 8380
 8381    cx.assert_editor_state(indoc! {"
 8382        line 1
 8383        line 2
 8384        lineˇ 3
 8385        line 4
 8386        line 5
 8387        line 6
 8388        line 7
 8389        line 8
 8390        line 9
 8391        line 10
 8392    "});
 8393}
 8394
 8395#[gpui::test]
 8396async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8397    init_test(cx, |_| {});
 8398
 8399    let mut cx = EditorTestContext::new(cx).await;
 8400    cx.set_state(
 8401        r#"let foo = 2;
 8402lˇet foo = 2;
 8403let fooˇ = 2;
 8404let foo = 2;
 8405let foo = ˇ2;"#,
 8406    );
 8407
 8408    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8409        .unwrap();
 8410    cx.assert_editor_state(
 8411        r#"let foo = 2;
 8412«letˇ» foo = 2;
 8413let «fooˇ» = 2;
 8414let foo = 2;
 8415let foo = «2ˇ»;"#,
 8416    );
 8417
 8418    // noop for multiple selections with different contents
 8419    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8420        .unwrap();
 8421    cx.assert_editor_state(
 8422        r#"let foo = 2;
 8423«letˇ» foo = 2;
 8424let «fooˇ» = 2;
 8425let foo = 2;
 8426let foo = «2ˇ»;"#,
 8427    );
 8428
 8429    // Test last selection direction should be preserved
 8430    cx.set_state(
 8431        r#"let foo = 2;
 8432let foo = 2;
 8433let «fooˇ» = 2;
 8434let «ˇfoo» = 2;
 8435let foo = 2;"#,
 8436    );
 8437
 8438    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8439        .unwrap();
 8440    cx.assert_editor_state(
 8441        r#"let foo = 2;
 8442let foo = 2;
 8443let «fooˇ» = 2;
 8444let «ˇfoo» = 2;
 8445let «ˇfoo» = 2;"#,
 8446    );
 8447}
 8448
 8449#[gpui::test]
 8450async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8451    init_test(cx, |_| {});
 8452
 8453    let mut cx =
 8454        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8455
 8456    cx.assert_editor_state(indoc! {"
 8457        ˇbbb
 8458        ccc
 8459
 8460        bbb
 8461        ccc
 8462        "});
 8463    cx.dispatch_action(SelectPrevious::default());
 8464    cx.assert_editor_state(indoc! {"
 8465                «bbbˇ»
 8466                ccc
 8467
 8468                bbb
 8469                ccc
 8470                "});
 8471    cx.dispatch_action(SelectPrevious::default());
 8472    cx.assert_editor_state(indoc! {"
 8473                «bbbˇ»
 8474                ccc
 8475
 8476                «bbbˇ»
 8477                ccc
 8478                "});
 8479}
 8480
 8481#[gpui::test]
 8482async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8483    init_test(cx, |_| {});
 8484
 8485    let mut cx = EditorTestContext::new(cx).await;
 8486    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8487
 8488    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8489        .unwrap();
 8490    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8491
 8492    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8493        .unwrap();
 8494    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8495
 8496    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8497    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8498
 8499    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8500    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8501
 8502    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8503        .unwrap();
 8504    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 8505
 8506    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8507        .unwrap();
 8508    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8509}
 8510
 8511#[gpui::test]
 8512async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8513    init_test(cx, |_| {});
 8514
 8515    let mut cx = EditorTestContext::new(cx).await;
 8516    cx.set_state("");
 8517
 8518    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8519        .unwrap();
 8520    cx.assert_editor_state("«aˇ»");
 8521    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8522        .unwrap();
 8523    cx.assert_editor_state("«aˇ»");
 8524}
 8525
 8526#[gpui::test]
 8527async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8528    init_test(cx, |_| {});
 8529
 8530    let mut cx = EditorTestContext::new(cx).await;
 8531    cx.set_state(
 8532        r#"let foo = 2;
 8533lˇet foo = 2;
 8534let fooˇ = 2;
 8535let foo = 2;
 8536let foo = ˇ2;"#,
 8537    );
 8538
 8539    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8540        .unwrap();
 8541    cx.assert_editor_state(
 8542        r#"let foo = 2;
 8543«letˇ» foo = 2;
 8544let «fooˇ» = 2;
 8545let foo = 2;
 8546let foo = «2ˇ»;"#,
 8547    );
 8548
 8549    // noop for multiple selections with different contents
 8550    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8551        .unwrap();
 8552    cx.assert_editor_state(
 8553        r#"let foo = 2;
 8554«letˇ» foo = 2;
 8555let «fooˇ» = 2;
 8556let foo = 2;
 8557let foo = «2ˇ»;"#,
 8558    );
 8559}
 8560
 8561#[gpui::test]
 8562async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 8563    init_test(cx, |_| {});
 8564
 8565    let mut cx = EditorTestContext::new(cx).await;
 8566    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8567
 8568    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8569        .unwrap();
 8570    // selection direction is preserved
 8571    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8572
 8573    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8574        .unwrap();
 8575    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8576
 8577    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8578    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8579
 8580    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8581    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8582
 8583    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8584        .unwrap();
 8585    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 8586
 8587    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8588        .unwrap();
 8589    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 8590}
 8591
 8592#[gpui::test]
 8593async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 8594    init_test(cx, |_| {});
 8595
 8596    let language = Arc::new(Language::new(
 8597        LanguageConfig::default(),
 8598        Some(tree_sitter_rust::LANGUAGE.into()),
 8599    ));
 8600
 8601    let text = r#"
 8602        use mod1::mod2::{mod3, mod4};
 8603
 8604        fn fn_1(param1: bool, param2: &str) {
 8605            let var1 = "text";
 8606        }
 8607    "#
 8608    .unindent();
 8609
 8610    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8611    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8612    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8613
 8614    editor
 8615        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8616        .await;
 8617
 8618    editor.update_in(cx, |editor, window, cx| {
 8619        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8620            s.select_display_ranges([
 8621                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 8622                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 8623                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 8624            ]);
 8625        });
 8626        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8627    });
 8628    editor.update(cx, |editor, cx| {
 8629        assert_text_with_selections(
 8630            editor,
 8631            indoc! {r#"
 8632                use mod1::mod2::{mod3, «mod4ˇ»};
 8633
 8634                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8635                    let var1 = "«ˇtext»";
 8636                }
 8637            "#},
 8638            cx,
 8639        );
 8640    });
 8641
 8642    editor.update_in(cx, |editor, window, cx| {
 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    assert_eq!(
 8663        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8664        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8665    );
 8666
 8667    // Trying to expand the selected syntax node one more time has no effect.
 8668    editor.update_in(cx, |editor, window, cx| {
 8669        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8670    });
 8671    assert_eq!(
 8672        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8673        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8674    );
 8675
 8676    editor.update_in(cx, |editor, window, cx| {
 8677        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8678    });
 8679    editor.update(cx, |editor, cx| {
 8680        assert_text_with_selections(
 8681            editor,
 8682            indoc! {r#"
 8683                use mod1::mod2::«{mod3, mod4}ˇ»;
 8684
 8685                «ˇfn fn_1(param1: bool, param2: &str) {
 8686                    let var1 = "text";
 8687 8688            "#},
 8689            cx,
 8690        );
 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, moˇd4};
 8718
 8719                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8720                    let var1 = "teˇxt";
 8721                }
 8722            "#},
 8723            cx,
 8724        );
 8725    });
 8726
 8727    // Trying to shrink the selected syntax node one more time has no effect.
 8728    editor.update_in(cx, |editor, window, cx| {
 8729        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8730    });
 8731    editor.update_in(cx, |editor, _, cx| {
 8732        assert_text_with_selections(
 8733            editor,
 8734            indoc! {r#"
 8735                use mod1::mod2::{mod3, moˇd4};
 8736
 8737                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8738                    let var1 = "teˇxt";
 8739                }
 8740            "#},
 8741            cx,
 8742        );
 8743    });
 8744
 8745    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 8746    // a fold.
 8747    editor.update_in(cx, |editor, window, cx| {
 8748        editor.fold_creases(
 8749            vec![
 8750                Crease::simple(
 8751                    Point::new(0, 21)..Point::new(0, 24),
 8752                    FoldPlaceholder::test(),
 8753                ),
 8754                Crease::simple(
 8755                    Point::new(3, 20)..Point::new(3, 22),
 8756                    FoldPlaceholder::test(),
 8757                ),
 8758            ],
 8759            true,
 8760            window,
 8761            cx,
 8762        );
 8763        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8764    });
 8765    editor.update(cx, |editor, cx| {
 8766        assert_text_with_selections(
 8767            editor,
 8768            indoc! {r#"
 8769                use mod1::mod2::«{mod3, mod4}ˇ»;
 8770
 8771                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8772                    let var1 = "«ˇtext»";
 8773                }
 8774            "#},
 8775            cx,
 8776        );
 8777    });
 8778}
 8779
 8780#[gpui::test]
 8781async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 8782    init_test(cx, |_| {});
 8783
 8784    let language = Arc::new(Language::new(
 8785        LanguageConfig::default(),
 8786        Some(tree_sitter_rust::LANGUAGE.into()),
 8787    ));
 8788
 8789    let text = "let a = 2;";
 8790
 8791    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8792    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8793    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8794
 8795    editor
 8796        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8797        .await;
 8798
 8799    // Test case 1: Cursor at end of word
 8800    editor.update_in(cx, |editor, window, cx| {
 8801        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8802            s.select_display_ranges([
 8803                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 8804            ]);
 8805        });
 8806    });
 8807    editor.update(cx, |editor, cx| {
 8808        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 8809    });
 8810    editor.update_in(cx, |editor, window, cx| {
 8811        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8812    });
 8813    editor.update(cx, |editor, cx| {
 8814        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 8815    });
 8816    editor.update_in(cx, |editor, window, cx| {
 8817        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8818    });
 8819    editor.update(cx, |editor, cx| {
 8820        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 8821    });
 8822
 8823    // Test case 2: Cursor at end of statement
 8824    editor.update_in(cx, |editor, window, cx| {
 8825        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8826            s.select_display_ranges([
 8827                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 8828            ]);
 8829        });
 8830    });
 8831    editor.update(cx, |editor, cx| {
 8832        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 8833    });
 8834    editor.update_in(cx, |editor, window, cx| {
 8835        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8836    });
 8837    editor.update(cx, |editor, cx| {
 8838        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 8839    });
 8840}
 8841
 8842#[gpui::test]
 8843async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 8844    init_test(cx, |_| {});
 8845
 8846    let language = Arc::new(Language::new(
 8847        LanguageConfig {
 8848            name: "JavaScript".into(),
 8849            ..Default::default()
 8850        },
 8851        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8852    ));
 8853
 8854    let text = r#"
 8855        let a = {
 8856            key: "value",
 8857        };
 8858    "#
 8859    .unindent();
 8860
 8861    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8862    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8863    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8864
 8865    editor
 8866        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8867        .await;
 8868
 8869    // Test case 1: Cursor after '{'
 8870    editor.update_in(cx, |editor, window, cx| {
 8871        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8872            s.select_display_ranges([
 8873                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 8874            ]);
 8875        });
 8876    });
 8877    editor.update(cx, |editor, cx| {
 8878        assert_text_with_selections(
 8879            editor,
 8880            indoc! {r#"
 8881                let a = {ˇ
 8882                    key: "value",
 8883                };
 8884            "#},
 8885            cx,
 8886        );
 8887    });
 8888    editor.update_in(cx, |editor, window, cx| {
 8889        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8890    });
 8891    editor.update(cx, |editor, cx| {
 8892        assert_text_with_selections(
 8893            editor,
 8894            indoc! {r#"
 8895                let a = «ˇ{
 8896                    key: "value",
 8897                }»;
 8898            "#},
 8899            cx,
 8900        );
 8901    });
 8902
 8903    // Test case 2: Cursor after ':'
 8904    editor.update_in(cx, |editor, window, cx| {
 8905        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8906            s.select_display_ranges([
 8907                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 8908            ]);
 8909        });
 8910    });
 8911    editor.update(cx, |editor, cx| {
 8912        assert_text_with_selections(
 8913            editor,
 8914            indoc! {r#"
 8915                let a = {
 8916                    key:ˇ "value",
 8917                };
 8918            "#},
 8919            cx,
 8920        );
 8921    });
 8922    editor.update_in(cx, |editor, window, cx| {
 8923        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8924    });
 8925    editor.update(cx, |editor, cx| {
 8926        assert_text_with_selections(
 8927            editor,
 8928            indoc! {r#"
 8929                let a = {
 8930                    «ˇkey: "value"»,
 8931                };
 8932            "#},
 8933            cx,
 8934        );
 8935    });
 8936    editor.update_in(cx, |editor, window, cx| {
 8937        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8938    });
 8939    editor.update(cx, |editor, cx| {
 8940        assert_text_with_selections(
 8941            editor,
 8942            indoc! {r#"
 8943                let a = «ˇ{
 8944                    key: "value",
 8945                }»;
 8946            "#},
 8947            cx,
 8948        );
 8949    });
 8950
 8951    // Test case 3: Cursor after ','
 8952    editor.update_in(cx, |editor, window, cx| {
 8953        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8954            s.select_display_ranges([
 8955                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 8956            ]);
 8957        });
 8958    });
 8959    editor.update(cx, |editor, cx| {
 8960        assert_text_with_selections(
 8961            editor,
 8962            indoc! {r#"
 8963                let a = {
 8964                    key: "value",ˇ
 8965                };
 8966            "#},
 8967            cx,
 8968        );
 8969    });
 8970    editor.update_in(cx, |editor, window, cx| {
 8971        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8972    });
 8973    editor.update(cx, |editor, cx| {
 8974        assert_text_with_selections(
 8975            editor,
 8976            indoc! {r#"
 8977                let a = «ˇ{
 8978                    key: "value",
 8979                }»;
 8980            "#},
 8981            cx,
 8982        );
 8983    });
 8984
 8985    // Test case 4: Cursor after ';'
 8986    editor.update_in(cx, |editor, window, cx| {
 8987        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8988            s.select_display_ranges([
 8989                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 8990            ]);
 8991        });
 8992    });
 8993    editor.update(cx, |editor, cx| {
 8994        assert_text_with_selections(
 8995            editor,
 8996            indoc! {r#"
 8997                let a = {
 8998                    key: "value",
 8999                };ˇ
 9000            "#},
 9001            cx,
 9002        );
 9003    });
 9004    editor.update_in(cx, |editor, window, cx| {
 9005        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9006    });
 9007    editor.update(cx, |editor, cx| {
 9008        assert_text_with_selections(
 9009            editor,
 9010            indoc! {r#"
 9011                «ˇlet a = {
 9012                    key: "value",
 9013                };
 9014                »"#},
 9015            cx,
 9016        );
 9017    });
 9018}
 9019
 9020#[gpui::test]
 9021async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 9022    init_test(cx, |_| {});
 9023
 9024    let language = Arc::new(Language::new(
 9025        LanguageConfig::default(),
 9026        Some(tree_sitter_rust::LANGUAGE.into()),
 9027    ));
 9028
 9029    let text = r#"
 9030        use mod1::mod2::{mod3, mod4};
 9031
 9032        fn fn_1(param1: bool, param2: &str) {
 9033            let var1 = "hello world";
 9034        }
 9035    "#
 9036    .unindent();
 9037
 9038    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9039    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9040    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9041
 9042    editor
 9043        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9044        .await;
 9045
 9046    // Test 1: Cursor on a letter of a string word
 9047    editor.update_in(cx, |editor, window, cx| {
 9048        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9049            s.select_display_ranges([
 9050                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 9051            ]);
 9052        });
 9053    });
 9054    editor.update_in(cx, |editor, window, cx| {
 9055        assert_text_with_selections(
 9056            editor,
 9057            indoc! {r#"
 9058                use mod1::mod2::{mod3, mod4};
 9059
 9060                fn fn_1(param1: bool, param2: &str) {
 9061                    let var1 = "hˇello world";
 9062                }
 9063            "#},
 9064            cx,
 9065        );
 9066        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9067        assert_text_with_selections(
 9068            editor,
 9069            indoc! {r#"
 9070                use mod1::mod2::{mod3, mod4};
 9071
 9072                fn fn_1(param1: bool, param2: &str) {
 9073                    let var1 = "«ˇhello» world";
 9074                }
 9075            "#},
 9076            cx,
 9077        );
 9078    });
 9079
 9080    // Test 2: Partial selection within a word
 9081    editor.update_in(cx, |editor, window, cx| {
 9082        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9083            s.select_display_ranges([
 9084                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9085            ]);
 9086        });
 9087    });
 9088    editor.update_in(cx, |editor, window, cx| {
 9089        assert_text_with_selections(
 9090            editor,
 9091            indoc! {r#"
 9092                use mod1::mod2::{mod3, mod4};
 9093
 9094                fn fn_1(param1: bool, param2: &str) {
 9095                    let var1 = "h«elˇ»lo world";
 9096                }
 9097            "#},
 9098            cx,
 9099        );
 9100        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9101        assert_text_with_selections(
 9102            editor,
 9103            indoc! {r#"
 9104                use mod1::mod2::{mod3, mod4};
 9105
 9106                fn fn_1(param1: bool, param2: &str) {
 9107                    let var1 = "«ˇhello» world";
 9108                }
 9109            "#},
 9110            cx,
 9111        );
 9112    });
 9113
 9114    // Test 3: Complete word already selected
 9115    editor.update_in(cx, |editor, window, cx| {
 9116        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9117            s.select_display_ranges([
 9118                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9119            ]);
 9120        });
 9121    });
 9122    editor.update_in(cx, |editor, window, cx| {
 9123        assert_text_with_selections(
 9124            editor,
 9125            indoc! {r#"
 9126                use mod1::mod2::{mod3, mod4};
 9127
 9128                fn fn_1(param1: bool, param2: &str) {
 9129                    let var1 = "«helloˇ» world";
 9130                }
 9131            "#},
 9132            cx,
 9133        );
 9134        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9135        assert_text_with_selections(
 9136            editor,
 9137            indoc! {r#"
 9138                use mod1::mod2::{mod3, mod4};
 9139
 9140                fn fn_1(param1: bool, param2: &str) {
 9141                    let var1 = "«hello worldˇ»";
 9142                }
 9143            "#},
 9144            cx,
 9145        );
 9146    });
 9147
 9148    // Test 4: Selection spanning across words
 9149    editor.update_in(cx, |editor, window, cx| {
 9150        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9151            s.select_display_ranges([
 9152                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9153            ]);
 9154        });
 9155    });
 9156    editor.update_in(cx, |editor, window, cx| {
 9157        assert_text_with_selections(
 9158            editor,
 9159            indoc! {r#"
 9160                use mod1::mod2::{mod3, mod4};
 9161
 9162                fn fn_1(param1: bool, param2: &str) {
 9163                    let var1 = "hel«lo woˇ»rld";
 9164                }
 9165            "#},
 9166            cx,
 9167        );
 9168        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9169        assert_text_with_selections(
 9170            editor,
 9171            indoc! {r#"
 9172                use mod1::mod2::{mod3, mod4};
 9173
 9174                fn fn_1(param1: bool, param2: &str) {
 9175                    let var1 = "«ˇhello world»";
 9176                }
 9177            "#},
 9178            cx,
 9179        );
 9180    });
 9181
 9182    // Test 5: Expansion beyond string
 9183    editor.update_in(cx, |editor, window, cx| {
 9184        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 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
 9200#[gpui::test]
 9201async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9202    init_test(cx, |_| {});
 9203
 9204    let mut cx = EditorTestContext::new(cx).await;
 9205
 9206    let language = Arc::new(Language::new(
 9207        LanguageConfig::default(),
 9208        Some(tree_sitter_rust::LANGUAGE.into()),
 9209    ));
 9210
 9211    cx.update_buffer(|buffer, cx| {
 9212        buffer.set_language(Some(language), cx);
 9213    });
 9214
 9215    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9216    cx.update_editor(|editor, window, cx| {
 9217        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9218    });
 9219
 9220    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9221
 9222    cx.set_state(indoc! { r#"fn a() {
 9223          // what
 9224          // a
 9225          // ˇlong
 9226          // method
 9227          // I
 9228          // sure
 9229          // hope
 9230          // it
 9231          // works
 9232    }"# });
 9233
 9234    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
 9235    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 9236    cx.update(|_, cx| {
 9237        multi_buffer.update(cx, |multi_buffer, cx| {
 9238            multi_buffer.set_excerpts_for_path(
 9239                PathKey::for_buffer(&buffer, cx),
 9240                buffer,
 9241                [Point::new(1, 0)..Point::new(1, 0)],
 9242                3,
 9243                cx,
 9244            );
 9245        });
 9246    });
 9247
 9248    let editor2 = cx.new_window_entity(|window, cx| {
 9249        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 9250    });
 9251
 9252    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
 9253    cx.update_editor(|editor, window, cx| {
 9254        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9255            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
 9256        })
 9257    });
 9258
 9259    cx.assert_editor_state(indoc! { "
 9260        fn a() {
 9261              // what
 9262              // a
 9263        ˇ      // long
 9264              // method"});
 9265
 9266    cx.update_editor(|editor, window, cx| {
 9267        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9268    });
 9269
 9270    // Although we could potentially make the action work when the syntax node
 9271    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
 9272    // did. Maybe we could also expand the excerpt to contain the range?
 9273    cx.assert_editor_state(indoc! { "
 9274        fn a() {
 9275              // what
 9276              // a
 9277        ˇ      // long
 9278              // method"});
 9279}
 9280
 9281#[gpui::test]
 9282async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9283    init_test(cx, |_| {});
 9284
 9285    let base_text = r#"
 9286        impl A {
 9287            // this is an uncommitted comment
 9288
 9289            fn b() {
 9290                c();
 9291            }
 9292
 9293            // this is another uncommitted comment
 9294
 9295            fn d() {
 9296                // e
 9297                // f
 9298            }
 9299        }
 9300
 9301        fn g() {
 9302            // h
 9303        }
 9304    "#
 9305    .unindent();
 9306
 9307    let text = r#"
 9308        ˇimpl A {
 9309
 9310            fn b() {
 9311                c();
 9312            }
 9313
 9314            fn d() {
 9315                // e
 9316                // f
 9317            }
 9318        }
 9319
 9320        fn g() {
 9321            // h
 9322        }
 9323    "#
 9324    .unindent();
 9325
 9326    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9327    cx.set_state(&text);
 9328    cx.set_head_text(&base_text);
 9329    cx.update_editor(|editor, window, cx| {
 9330        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9331    });
 9332
 9333    cx.assert_state_with_diff(
 9334        "
 9335        ˇimpl A {
 9336      -     // this is an uncommitted comment
 9337
 9338            fn b() {
 9339                c();
 9340            }
 9341
 9342      -     // this is another uncommitted comment
 9343      -
 9344            fn d() {
 9345                // e
 9346                // f
 9347            }
 9348        }
 9349
 9350        fn g() {
 9351            // h
 9352        }
 9353    "
 9354        .unindent(),
 9355    );
 9356
 9357    let expected_display_text = "
 9358        impl A {
 9359            // this is an uncommitted comment
 9360
 9361            fn b() {
 9362 9363            }
 9364
 9365            // this is another uncommitted comment
 9366
 9367            fn d() {
 9368 9369            }
 9370        }
 9371
 9372        fn g() {
 9373 9374        }
 9375        "
 9376    .unindent();
 9377
 9378    cx.update_editor(|editor, window, cx| {
 9379        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9380        assert_eq!(editor.display_text(cx), expected_display_text);
 9381    });
 9382}
 9383
 9384#[gpui::test]
 9385async fn test_autoindent(cx: &mut TestAppContext) {
 9386    init_test(cx, |_| {});
 9387
 9388    let language = Arc::new(
 9389        Language::new(
 9390            LanguageConfig {
 9391                brackets: BracketPairConfig {
 9392                    pairs: vec![
 9393                        BracketPair {
 9394                            start: "{".to_string(),
 9395                            end: "}".to_string(),
 9396                            close: false,
 9397                            surround: false,
 9398                            newline: true,
 9399                        },
 9400                        BracketPair {
 9401                            start: "(".to_string(),
 9402                            end: ")".to_string(),
 9403                            close: false,
 9404                            surround: false,
 9405                            newline: true,
 9406                        },
 9407                    ],
 9408                    ..Default::default()
 9409                },
 9410                ..Default::default()
 9411            },
 9412            Some(tree_sitter_rust::LANGUAGE.into()),
 9413        )
 9414        .with_indents_query(
 9415            r#"
 9416                (_ "(" ")" @end) @indent
 9417                (_ "{" "}" @end) @indent
 9418            "#,
 9419        )
 9420        .unwrap(),
 9421    );
 9422
 9423    let text = "fn a() {}";
 9424
 9425    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9426    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9427    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9428    editor
 9429        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9430        .await;
 9431
 9432    editor.update_in(cx, |editor, window, cx| {
 9433        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9434            s.select_ranges([5..5, 8..8, 9..9])
 9435        });
 9436        editor.newline(&Newline, window, cx);
 9437        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9438        assert_eq!(
 9439            editor.selections.ranges(cx),
 9440            &[
 9441                Point::new(1, 4)..Point::new(1, 4),
 9442                Point::new(3, 4)..Point::new(3, 4),
 9443                Point::new(5, 0)..Point::new(5, 0)
 9444            ]
 9445        );
 9446    });
 9447}
 9448
 9449#[gpui::test]
 9450async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9451    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9452
 9453    let language = Arc::new(
 9454        Language::new(
 9455            LanguageConfig {
 9456                brackets: BracketPairConfig {
 9457                    pairs: vec![
 9458                        BracketPair {
 9459                            start: "{".to_string(),
 9460                            end: "}".to_string(),
 9461                            close: false,
 9462                            surround: false,
 9463                            newline: true,
 9464                        },
 9465                        BracketPair {
 9466                            start: "(".to_string(),
 9467                            end: ")".to_string(),
 9468                            close: false,
 9469                            surround: false,
 9470                            newline: true,
 9471                        },
 9472                    ],
 9473                    ..Default::default()
 9474                },
 9475                ..Default::default()
 9476            },
 9477            Some(tree_sitter_rust::LANGUAGE.into()),
 9478        )
 9479        .with_indents_query(
 9480            r#"
 9481                (_ "(" ")" @end) @indent
 9482                (_ "{" "}" @end) @indent
 9483            "#,
 9484        )
 9485        .unwrap(),
 9486    );
 9487
 9488    let text = "fn a() {}";
 9489
 9490    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9491    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9492    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9493    editor
 9494        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9495        .await;
 9496
 9497    editor.update_in(cx, |editor, window, cx| {
 9498        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9499            s.select_ranges([5..5, 8..8, 9..9])
 9500        });
 9501        editor.newline(&Newline, window, cx);
 9502        assert_eq!(
 9503            editor.text(cx),
 9504            indoc!(
 9505                "
 9506                fn a(
 9507
 9508                ) {
 9509
 9510                }
 9511                "
 9512            )
 9513        );
 9514        assert_eq!(
 9515            editor.selections.ranges(cx),
 9516            &[
 9517                Point::new(1, 0)..Point::new(1, 0),
 9518                Point::new(3, 0)..Point::new(3, 0),
 9519                Point::new(5, 0)..Point::new(5, 0)
 9520            ]
 9521        );
 9522    });
 9523}
 9524
 9525#[gpui::test]
 9526async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 9527    init_test(cx, |settings| {
 9528        settings.defaults.auto_indent = Some(true);
 9529        settings.languages.0.insert(
 9530            "python".into(),
 9531            LanguageSettingsContent {
 9532                auto_indent: Some(false),
 9533                ..Default::default()
 9534            },
 9535        );
 9536    });
 9537
 9538    let mut cx = EditorTestContext::new(cx).await;
 9539
 9540    let injected_language = Arc::new(
 9541        Language::new(
 9542            LanguageConfig {
 9543                brackets: BracketPairConfig {
 9544                    pairs: vec![
 9545                        BracketPair {
 9546                            start: "{".to_string(),
 9547                            end: "}".to_string(),
 9548                            close: false,
 9549                            surround: false,
 9550                            newline: true,
 9551                        },
 9552                        BracketPair {
 9553                            start: "(".to_string(),
 9554                            end: ")".to_string(),
 9555                            close: true,
 9556                            surround: false,
 9557                            newline: true,
 9558                        },
 9559                    ],
 9560                    ..Default::default()
 9561                },
 9562                name: "python".into(),
 9563                ..Default::default()
 9564            },
 9565            Some(tree_sitter_python::LANGUAGE.into()),
 9566        )
 9567        .with_indents_query(
 9568            r#"
 9569                (_ "(" ")" @end) @indent
 9570                (_ "{" "}" @end) @indent
 9571            "#,
 9572        )
 9573        .unwrap(),
 9574    );
 9575
 9576    let language = Arc::new(
 9577        Language::new(
 9578            LanguageConfig {
 9579                brackets: BracketPairConfig {
 9580                    pairs: vec![
 9581                        BracketPair {
 9582                            start: "{".to_string(),
 9583                            end: "}".to_string(),
 9584                            close: false,
 9585                            surround: false,
 9586                            newline: true,
 9587                        },
 9588                        BracketPair {
 9589                            start: "(".to_string(),
 9590                            end: ")".to_string(),
 9591                            close: true,
 9592                            surround: false,
 9593                            newline: true,
 9594                        },
 9595                    ],
 9596                    ..Default::default()
 9597                },
 9598                name: LanguageName::new("rust"),
 9599                ..Default::default()
 9600            },
 9601            Some(tree_sitter_rust::LANGUAGE.into()),
 9602        )
 9603        .with_indents_query(
 9604            r#"
 9605                (_ "(" ")" @end) @indent
 9606                (_ "{" "}" @end) @indent
 9607            "#,
 9608        )
 9609        .unwrap()
 9610        .with_injection_query(
 9611            r#"
 9612            (macro_invocation
 9613                macro: (identifier) @_macro_name
 9614                (token_tree) @injection.content
 9615                (#set! injection.language "python"))
 9616           "#,
 9617        )
 9618        .unwrap(),
 9619    );
 9620
 9621    cx.language_registry().add(injected_language);
 9622    cx.language_registry().add(language.clone());
 9623
 9624    cx.update_buffer(|buffer, cx| {
 9625        buffer.set_language(Some(language), cx);
 9626    });
 9627
 9628    cx.set_state(r#"struct A {ˇ}"#);
 9629
 9630    cx.update_editor(|editor, window, cx| {
 9631        editor.newline(&Default::default(), window, cx);
 9632    });
 9633
 9634    cx.assert_editor_state(indoc!(
 9635        "struct A {
 9636            ˇ
 9637        }"
 9638    ));
 9639
 9640    cx.set_state(r#"select_biased!(ˇ)"#);
 9641
 9642    cx.update_editor(|editor, window, cx| {
 9643        editor.newline(&Default::default(), window, cx);
 9644        editor.handle_input("def ", window, cx);
 9645        editor.handle_input("(", window, cx);
 9646        editor.newline(&Default::default(), window, cx);
 9647        editor.handle_input("a", window, cx);
 9648    });
 9649
 9650    cx.assert_editor_state(indoc!(
 9651        "select_biased!(
 9652        def (
 9653 9654        )
 9655        )"
 9656    ));
 9657}
 9658
 9659#[gpui::test]
 9660async fn test_autoindent_selections(cx: &mut TestAppContext) {
 9661    init_test(cx, |_| {});
 9662
 9663    {
 9664        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9665        cx.set_state(indoc! {"
 9666            impl A {
 9667
 9668                fn b() {}
 9669
 9670            «fn c() {
 9671
 9672            }ˇ»
 9673            }
 9674        "});
 9675
 9676        cx.update_editor(|editor, window, cx| {
 9677            editor.autoindent(&Default::default(), window, cx);
 9678        });
 9679
 9680        cx.assert_editor_state(indoc! {"
 9681            impl A {
 9682
 9683                fn b() {}
 9684
 9685                «fn c() {
 9686
 9687                }ˇ»
 9688            }
 9689        "});
 9690    }
 9691
 9692    {
 9693        let mut cx = EditorTestContext::new_multibuffer(
 9694            cx,
 9695            [indoc! { "
 9696                impl A {
 9697                «
 9698                // a
 9699                fn b(){}
 9700                »
 9701                «
 9702                    }
 9703                    fn c(){}
 9704                »
 9705            "}],
 9706        );
 9707
 9708        let buffer = cx.update_editor(|editor, _, cx| {
 9709            let buffer = editor.buffer().update(cx, |buffer, _| {
 9710                buffer.all_buffers().iter().next().unwrap().clone()
 9711            });
 9712            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 9713            buffer
 9714        });
 9715
 9716        cx.run_until_parked();
 9717        cx.update_editor(|editor, window, cx| {
 9718            editor.select_all(&Default::default(), window, cx);
 9719            editor.autoindent(&Default::default(), window, cx)
 9720        });
 9721        cx.run_until_parked();
 9722
 9723        cx.update(|_, cx| {
 9724            assert_eq!(
 9725                buffer.read(cx).text(),
 9726                indoc! { "
 9727                    impl A {
 9728
 9729                        // a
 9730                        fn b(){}
 9731
 9732
 9733                    }
 9734                    fn c(){}
 9735
 9736                " }
 9737            )
 9738        });
 9739    }
 9740}
 9741
 9742#[gpui::test]
 9743async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 9744    init_test(cx, |_| {});
 9745
 9746    let mut cx = EditorTestContext::new(cx).await;
 9747
 9748    let language = Arc::new(Language::new(
 9749        LanguageConfig {
 9750            brackets: BracketPairConfig {
 9751                pairs: vec![
 9752                    BracketPair {
 9753                        start: "{".to_string(),
 9754                        end: "}".to_string(),
 9755                        close: true,
 9756                        surround: true,
 9757                        newline: true,
 9758                    },
 9759                    BracketPair {
 9760                        start: "(".to_string(),
 9761                        end: ")".to_string(),
 9762                        close: true,
 9763                        surround: true,
 9764                        newline: true,
 9765                    },
 9766                    BracketPair {
 9767                        start: "/*".to_string(),
 9768                        end: " */".to_string(),
 9769                        close: true,
 9770                        surround: true,
 9771                        newline: true,
 9772                    },
 9773                    BracketPair {
 9774                        start: "[".to_string(),
 9775                        end: "]".to_string(),
 9776                        close: false,
 9777                        surround: false,
 9778                        newline: true,
 9779                    },
 9780                    BracketPair {
 9781                        start: "\"".to_string(),
 9782                        end: "\"".to_string(),
 9783                        close: true,
 9784                        surround: true,
 9785                        newline: false,
 9786                    },
 9787                    BracketPair {
 9788                        start: "<".to_string(),
 9789                        end: ">".to_string(),
 9790                        close: false,
 9791                        surround: true,
 9792                        newline: true,
 9793                    },
 9794                ],
 9795                ..Default::default()
 9796            },
 9797            autoclose_before: "})]".to_string(),
 9798            ..Default::default()
 9799        },
 9800        Some(tree_sitter_rust::LANGUAGE.into()),
 9801    ));
 9802
 9803    cx.language_registry().add(language.clone());
 9804    cx.update_buffer(|buffer, cx| {
 9805        buffer.set_language(Some(language), cx);
 9806    });
 9807
 9808    cx.set_state(
 9809        &r#"
 9810            🏀ˇ
 9811            εˇ
 9812            ❤️ˇ
 9813        "#
 9814        .unindent(),
 9815    );
 9816
 9817    // autoclose multiple nested brackets at multiple cursors
 9818    cx.update_editor(|editor, window, cx| {
 9819        editor.handle_input("{", window, cx);
 9820        editor.handle_input("{", window, cx);
 9821        editor.handle_input("{", window, cx);
 9822    });
 9823    cx.assert_editor_state(
 9824        &"
 9825            🏀{{{ˇ}}}
 9826            ε{{{ˇ}}}
 9827            ❤️{{{ˇ}}}
 9828        "
 9829        .unindent(),
 9830    );
 9831
 9832    // insert a different closing bracket
 9833    cx.update_editor(|editor, window, cx| {
 9834        editor.handle_input(")", window, cx);
 9835    });
 9836    cx.assert_editor_state(
 9837        &"
 9838            🏀{{{)ˇ}}}
 9839            ε{{{)ˇ}}}
 9840            ❤️{{{)ˇ}}}
 9841        "
 9842        .unindent(),
 9843    );
 9844
 9845    // skip over the auto-closed brackets when typing a closing bracket
 9846    cx.update_editor(|editor, window, cx| {
 9847        editor.move_right(&MoveRight, window, cx);
 9848        editor.handle_input("}", window, cx);
 9849        editor.handle_input("}", window, cx);
 9850        editor.handle_input("}", window, cx);
 9851    });
 9852    cx.assert_editor_state(
 9853        &"
 9854            🏀{{{)}}}}ˇ
 9855            ε{{{)}}}}ˇ
 9856            ❤️{{{)}}}}ˇ
 9857        "
 9858        .unindent(),
 9859    );
 9860
 9861    // autoclose multi-character pairs
 9862    cx.set_state(
 9863        &"
 9864            ˇ
 9865            ˇ
 9866        "
 9867        .unindent(),
 9868    );
 9869    cx.update_editor(|editor, window, cx| {
 9870        editor.handle_input("/", window, cx);
 9871        editor.handle_input("*", window, cx);
 9872    });
 9873    cx.assert_editor_state(
 9874        &"
 9875            /*ˇ */
 9876            /*ˇ */
 9877        "
 9878        .unindent(),
 9879    );
 9880
 9881    // one cursor autocloses a multi-character pair, one cursor
 9882    // does not autoclose.
 9883    cx.set_state(
 9884        &"
 9885 9886            ˇ
 9887        "
 9888        .unindent(),
 9889    );
 9890    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 9891    cx.assert_editor_state(
 9892        &"
 9893            /*ˇ */
 9894 9895        "
 9896        .unindent(),
 9897    );
 9898
 9899    // Don't autoclose if the next character isn't whitespace and isn't
 9900    // listed in the language's "autoclose_before" section.
 9901    cx.set_state("ˇa b");
 9902    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9903    cx.assert_editor_state("{ˇa b");
 9904
 9905    // Don't autoclose if `close` is false for the bracket pair
 9906    cx.set_state("ˇ");
 9907    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 9908    cx.assert_editor_state("");
 9909
 9910    // Surround with brackets if text is selected
 9911    cx.set_state("«aˇ» b");
 9912    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9913    cx.assert_editor_state("{«aˇ»} b");
 9914
 9915    // Autoclose when not immediately after a word character
 9916    cx.set_state("a ˇ");
 9917    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9918    cx.assert_editor_state("a \"ˇ\"");
 9919
 9920    // Autoclose pair where the start and end characters are the same
 9921    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9922    cx.assert_editor_state("a \"\"ˇ");
 9923
 9924    // Don't autoclose when immediately after a word character
 9925    cx.set_state("");
 9926    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9927    cx.assert_editor_state("a\"ˇ");
 9928
 9929    // Do autoclose when after a non-word character
 9930    cx.set_state("");
 9931    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9932    cx.assert_editor_state("{\"ˇ\"");
 9933
 9934    // Non identical pairs autoclose regardless of preceding character
 9935    cx.set_state("");
 9936    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9937    cx.assert_editor_state("a{ˇ}");
 9938
 9939    // Don't autoclose pair if autoclose is disabled
 9940    cx.set_state("ˇ");
 9941    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9942    cx.assert_editor_state("");
 9943
 9944    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 9945    cx.set_state("«aˇ» b");
 9946    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9947    cx.assert_editor_state("<«aˇ»> b");
 9948}
 9949
 9950#[gpui::test]
 9951async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 9952    init_test(cx, |settings| {
 9953        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9954    });
 9955
 9956    let mut cx = EditorTestContext::new(cx).await;
 9957
 9958    let language = Arc::new(Language::new(
 9959        LanguageConfig {
 9960            brackets: BracketPairConfig {
 9961                pairs: vec![
 9962                    BracketPair {
 9963                        start: "{".to_string(),
 9964                        end: "}".to_string(),
 9965                        close: true,
 9966                        surround: true,
 9967                        newline: true,
 9968                    },
 9969                    BracketPair {
 9970                        start: "(".to_string(),
 9971                        end: ")".to_string(),
 9972                        close: true,
 9973                        surround: true,
 9974                        newline: true,
 9975                    },
 9976                    BracketPair {
 9977                        start: "[".to_string(),
 9978                        end: "]".to_string(),
 9979                        close: false,
 9980                        surround: false,
 9981                        newline: true,
 9982                    },
 9983                ],
 9984                ..Default::default()
 9985            },
 9986            autoclose_before: "})]".to_string(),
 9987            ..Default::default()
 9988        },
 9989        Some(tree_sitter_rust::LANGUAGE.into()),
 9990    ));
 9991
 9992    cx.language_registry().add(language.clone());
 9993    cx.update_buffer(|buffer, cx| {
 9994        buffer.set_language(Some(language), cx);
 9995    });
 9996
 9997    cx.set_state(
 9998        &"
 9999            ˇ
10000            ˇ
10001            ˇ
10002        "
10003        .unindent(),
10004    );
10005
10006    // ensure only matching closing brackets are skipped over
10007    cx.update_editor(|editor, window, cx| {
10008        editor.handle_input("}", window, cx);
10009        editor.move_left(&MoveLeft, window, cx);
10010        editor.handle_input(")", window, cx);
10011        editor.move_left(&MoveLeft, window, cx);
10012    });
10013    cx.assert_editor_state(
10014        &"
10015            ˇ)}
10016            ˇ)}
10017            ˇ)}
10018        "
10019        .unindent(),
10020    );
10021
10022    // skip-over closing brackets at multiple cursors
10023    cx.update_editor(|editor, window, cx| {
10024        editor.handle_input(")", window, cx);
10025        editor.handle_input("}", window, cx);
10026    });
10027    cx.assert_editor_state(
10028        &"
10029            )}ˇ
10030            )}ˇ
10031            )}ˇ
10032        "
10033        .unindent(),
10034    );
10035
10036    // ignore non-close brackets
10037    cx.update_editor(|editor, window, cx| {
10038        editor.handle_input("]", window, cx);
10039        editor.move_left(&MoveLeft, window, cx);
10040        editor.handle_input("]", window, cx);
10041    });
10042    cx.assert_editor_state(
10043        &"
10044            )}]ˇ]
10045            )}]ˇ]
10046            )}]ˇ]
10047        "
10048        .unindent(),
10049    );
10050}
10051
10052#[gpui::test]
10053async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
10054    init_test(cx, |_| {});
10055
10056    let mut cx = EditorTestContext::new(cx).await;
10057
10058    let html_language = Arc::new(
10059        Language::new(
10060            LanguageConfig {
10061                name: "HTML".into(),
10062                brackets: BracketPairConfig {
10063                    pairs: vec![
10064                        BracketPair {
10065                            start: "<".into(),
10066                            end: ">".into(),
10067                            close: true,
10068                            ..Default::default()
10069                        },
10070                        BracketPair {
10071                            start: "{".into(),
10072                            end: "}".into(),
10073                            close: true,
10074                            ..Default::default()
10075                        },
10076                        BracketPair {
10077                            start: "(".into(),
10078                            end: ")".into(),
10079                            close: true,
10080                            ..Default::default()
10081                        },
10082                    ],
10083                    ..Default::default()
10084                },
10085                autoclose_before: "})]>".into(),
10086                ..Default::default()
10087            },
10088            Some(tree_sitter_html::LANGUAGE.into()),
10089        )
10090        .with_injection_query(
10091            r#"
10092            (script_element
10093                (raw_text) @injection.content
10094                (#set! injection.language "javascript"))
10095            "#,
10096        )
10097        .unwrap(),
10098    );
10099
10100    let javascript_language = Arc::new(Language::new(
10101        LanguageConfig {
10102            name: "JavaScript".into(),
10103            brackets: BracketPairConfig {
10104                pairs: vec![
10105                    BracketPair {
10106                        start: "/*".into(),
10107                        end: " */".into(),
10108                        close: true,
10109                        ..Default::default()
10110                    },
10111                    BracketPair {
10112                        start: "{".into(),
10113                        end: "}".into(),
10114                        close: true,
10115                        ..Default::default()
10116                    },
10117                    BracketPair {
10118                        start: "(".into(),
10119                        end: ")".into(),
10120                        close: true,
10121                        ..Default::default()
10122                    },
10123                ],
10124                ..Default::default()
10125            },
10126            autoclose_before: "})]>".into(),
10127            ..Default::default()
10128        },
10129        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10130    ));
10131
10132    cx.language_registry().add(html_language.clone());
10133    cx.language_registry().add(javascript_language);
10134    cx.executor().run_until_parked();
10135
10136    cx.update_buffer(|buffer, cx| {
10137        buffer.set_language(Some(html_language), cx);
10138    });
10139
10140    cx.set_state(
10141        &r#"
10142            <body>ˇ
10143                <script>
10144                    var x = 1;ˇ
10145                </script>
10146            </body>ˇ
10147        "#
10148        .unindent(),
10149    );
10150
10151    // Precondition: different languages are active at different locations.
10152    cx.update_editor(|editor, window, cx| {
10153        let snapshot = editor.snapshot(window, cx);
10154        let cursors = editor.selections.ranges::<usize>(cx);
10155        let languages = cursors
10156            .iter()
10157            .map(|c| snapshot.language_at(c.start).unwrap().name())
10158            .collect::<Vec<_>>();
10159        assert_eq!(
10160            languages,
10161            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10162        );
10163    });
10164
10165    // Angle brackets autoclose in HTML, but not JavaScript.
10166    cx.update_editor(|editor, window, cx| {
10167        editor.handle_input("<", window, cx);
10168        editor.handle_input("a", window, cx);
10169    });
10170    cx.assert_editor_state(
10171        &r#"
10172            <body><aˇ>
10173                <script>
10174                    var x = 1;<aˇ
10175                </script>
10176            </body><aˇ>
10177        "#
10178        .unindent(),
10179    );
10180
10181    // Curly braces and parens autoclose in both HTML and JavaScript.
10182    cx.update_editor(|editor, window, cx| {
10183        editor.handle_input(" b=", window, cx);
10184        editor.handle_input("{", window, cx);
10185        editor.handle_input("c", window, cx);
10186        editor.handle_input("(", window, cx);
10187    });
10188    cx.assert_editor_state(
10189        &r#"
10190            <body><a b={c(ˇ)}>
10191                <script>
10192                    var x = 1;<a b={c(ˇ)}
10193                </script>
10194            </body><a b={c(ˇ)}>
10195        "#
10196        .unindent(),
10197    );
10198
10199    // Brackets that were already autoclosed are skipped.
10200    cx.update_editor(|editor, window, cx| {
10201        editor.handle_input(")", window, cx);
10202        editor.handle_input("d", window, cx);
10203        editor.handle_input("}", window, cx);
10204    });
10205    cx.assert_editor_state(
10206        &r#"
10207            <body><a b={c()d}ˇ>
10208                <script>
10209                    var x = 1;<a b={c()d}ˇ
10210                </script>
10211            </body><a b={c()d}ˇ>
10212        "#
10213        .unindent(),
10214    );
10215    cx.update_editor(|editor, window, cx| {
10216        editor.handle_input(">", window, cx);
10217    });
10218    cx.assert_editor_state(
10219        &r#"
10220            <body><a b={c()d}>ˇ
10221                <script>
10222                    var x = 1;<a b={c()d}>ˇ
10223                </script>
10224            </body><a b={c()d}>ˇ
10225        "#
10226        .unindent(),
10227    );
10228
10229    // Reset
10230    cx.set_state(
10231        &r#"
10232            <body>ˇ
10233                <script>
10234                    var x = 1;ˇ
10235                </script>
10236            </body>ˇ
10237        "#
10238        .unindent(),
10239    );
10240
10241    cx.update_editor(|editor, window, cx| {
10242        editor.handle_input("<", window, cx);
10243    });
10244    cx.assert_editor_state(
10245        &r#"
10246            <body><ˇ>
10247                <script>
10248                    var x = 1;<ˇ
10249                </script>
10250            </body><ˇ>
10251        "#
10252        .unindent(),
10253    );
10254
10255    // When backspacing, the closing angle brackets are removed.
10256    cx.update_editor(|editor, window, cx| {
10257        editor.backspace(&Backspace, window, cx);
10258    });
10259    cx.assert_editor_state(
10260        &r#"
10261            <body>ˇ
10262                <script>
10263                    var x = 1;ˇ
10264                </script>
10265            </body>ˇ
10266        "#
10267        .unindent(),
10268    );
10269
10270    // Block comments autoclose in JavaScript, but not HTML.
10271    cx.update_editor(|editor, window, cx| {
10272        editor.handle_input("/", window, cx);
10273        editor.handle_input("*", window, cx);
10274    });
10275    cx.assert_editor_state(
10276        &r#"
10277            <body>/*ˇ
10278                <script>
10279                    var x = 1;/*ˇ */
10280                </script>
10281            </body>/*ˇ
10282        "#
10283        .unindent(),
10284    );
10285}
10286
10287#[gpui::test]
10288async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10289    init_test(cx, |_| {});
10290
10291    let mut cx = EditorTestContext::new(cx).await;
10292
10293    let rust_language = Arc::new(
10294        Language::new(
10295            LanguageConfig {
10296                name: "Rust".into(),
10297                brackets: serde_json::from_value(json!([
10298                    { "start": "{", "end": "}", "close": true, "newline": true },
10299                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10300                ]))
10301                .unwrap(),
10302                autoclose_before: "})]>".into(),
10303                ..Default::default()
10304            },
10305            Some(tree_sitter_rust::LANGUAGE.into()),
10306        )
10307        .with_override_query("(string_literal) @string")
10308        .unwrap(),
10309    );
10310
10311    cx.language_registry().add(rust_language.clone());
10312    cx.update_buffer(|buffer, cx| {
10313        buffer.set_language(Some(rust_language), cx);
10314    });
10315
10316    cx.set_state(
10317        &r#"
10318            let x = ˇ
10319        "#
10320        .unindent(),
10321    );
10322
10323    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10324    cx.update_editor(|editor, window, cx| {
10325        editor.handle_input("\"", window, cx);
10326    });
10327    cx.assert_editor_state(
10328        &r#"
10329            let x = "ˇ"
10330        "#
10331        .unindent(),
10332    );
10333
10334    // Inserting another quotation mark. The cursor moves across the existing
10335    // automatically-inserted quotation mark.
10336    cx.update_editor(|editor, window, cx| {
10337        editor.handle_input("\"", window, cx);
10338    });
10339    cx.assert_editor_state(
10340        &r#"
10341            let x = ""ˇ
10342        "#
10343        .unindent(),
10344    );
10345
10346    // Reset
10347    cx.set_state(
10348        &r#"
10349            let x = ˇ
10350        "#
10351        .unindent(),
10352    );
10353
10354    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
10355    cx.update_editor(|editor, window, cx| {
10356        editor.handle_input("\"", window, cx);
10357        editor.handle_input(" ", window, cx);
10358        editor.move_left(&Default::default(), window, cx);
10359        editor.handle_input("\\", window, cx);
10360        editor.handle_input("\"", window, cx);
10361    });
10362    cx.assert_editor_state(
10363        &r#"
10364            let x = "\"ˇ "
10365        "#
10366        .unindent(),
10367    );
10368
10369    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10370    // mark. Nothing is inserted.
10371    cx.update_editor(|editor, window, cx| {
10372        editor.move_right(&Default::default(), window, cx);
10373        editor.handle_input("\"", window, cx);
10374    });
10375    cx.assert_editor_state(
10376        &r#"
10377            let x = "\" "ˇ
10378        "#
10379        .unindent(),
10380    );
10381}
10382
10383#[gpui::test]
10384async fn test_surround_with_pair(cx: &mut TestAppContext) {
10385    init_test(cx, |_| {});
10386
10387    let language = Arc::new(Language::new(
10388        LanguageConfig {
10389            brackets: BracketPairConfig {
10390                pairs: vec![
10391                    BracketPair {
10392                        start: "{".to_string(),
10393                        end: "}".to_string(),
10394                        close: true,
10395                        surround: true,
10396                        newline: true,
10397                    },
10398                    BracketPair {
10399                        start: "/* ".to_string(),
10400                        end: "*/".to_string(),
10401                        close: true,
10402                        surround: true,
10403                        ..Default::default()
10404                    },
10405                ],
10406                ..Default::default()
10407            },
10408            ..Default::default()
10409        },
10410        Some(tree_sitter_rust::LANGUAGE.into()),
10411    ));
10412
10413    let text = r#"
10414        a
10415        b
10416        c
10417    "#
10418    .unindent();
10419
10420    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10421    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10422    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10423    editor
10424        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10425        .await;
10426
10427    editor.update_in(cx, |editor, window, cx| {
10428        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10429            s.select_display_ranges([
10430                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10431                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10432                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
10433            ])
10434        });
10435
10436        editor.handle_input("{", window, cx);
10437        editor.handle_input("{", window, cx);
10438        editor.handle_input("{", window, cx);
10439        assert_eq!(
10440            editor.text(cx),
10441            "
10442                {{{a}}}
10443                {{{b}}}
10444                {{{c}}}
10445            "
10446            .unindent()
10447        );
10448        assert_eq!(
10449            editor.selections.display_ranges(cx),
10450            [
10451                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
10452                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
10453                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
10454            ]
10455        );
10456
10457        editor.undo(&Undo, window, cx);
10458        editor.undo(&Undo, window, cx);
10459        editor.undo(&Undo, window, cx);
10460        assert_eq!(
10461            editor.text(cx),
10462            "
10463                a
10464                b
10465                c
10466            "
10467            .unindent()
10468        );
10469        assert_eq!(
10470            editor.selections.display_ranges(cx),
10471            [
10472                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10473                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10474                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10475            ]
10476        );
10477
10478        // Ensure inserting the first character of a multi-byte bracket pair
10479        // doesn't surround the selections with the bracket.
10480        editor.handle_input("/", window, cx);
10481        assert_eq!(
10482            editor.text(cx),
10483            "
10484                /
10485                /
10486                /
10487            "
10488            .unindent()
10489        );
10490        assert_eq!(
10491            editor.selections.display_ranges(cx),
10492            [
10493                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10494                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10495                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10496            ]
10497        );
10498
10499        editor.undo(&Undo, window, cx);
10500        assert_eq!(
10501            editor.text(cx),
10502            "
10503                a
10504                b
10505                c
10506            "
10507            .unindent()
10508        );
10509        assert_eq!(
10510            editor.selections.display_ranges(cx),
10511            [
10512                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10513                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10514                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10515            ]
10516        );
10517
10518        // Ensure inserting the last character of a multi-byte bracket pair
10519        // doesn't surround the selections with the bracket.
10520        editor.handle_input("*", window, cx);
10521        assert_eq!(
10522            editor.text(cx),
10523            "
10524                *
10525                *
10526                *
10527            "
10528            .unindent()
10529        );
10530        assert_eq!(
10531            editor.selections.display_ranges(cx),
10532            [
10533                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10534                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10535                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10536            ]
10537        );
10538    });
10539}
10540
10541#[gpui::test]
10542async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
10543    init_test(cx, |_| {});
10544
10545    let language = Arc::new(Language::new(
10546        LanguageConfig {
10547            brackets: BracketPairConfig {
10548                pairs: vec![BracketPair {
10549                    start: "{".to_string(),
10550                    end: "}".to_string(),
10551                    close: true,
10552                    surround: true,
10553                    newline: true,
10554                }],
10555                ..Default::default()
10556            },
10557            autoclose_before: "}".to_string(),
10558            ..Default::default()
10559        },
10560        Some(tree_sitter_rust::LANGUAGE.into()),
10561    ));
10562
10563    let text = r#"
10564        a
10565        b
10566        c
10567    "#
10568    .unindent();
10569
10570    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10571    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10572    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10573    editor
10574        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10575        .await;
10576
10577    editor.update_in(cx, |editor, window, cx| {
10578        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10579            s.select_ranges([
10580                Point::new(0, 1)..Point::new(0, 1),
10581                Point::new(1, 1)..Point::new(1, 1),
10582                Point::new(2, 1)..Point::new(2, 1),
10583            ])
10584        });
10585
10586        editor.handle_input("{", window, cx);
10587        editor.handle_input("{", window, cx);
10588        editor.handle_input("_", window, cx);
10589        assert_eq!(
10590            editor.text(cx),
10591            "
10592                a{{_}}
10593                b{{_}}
10594                c{{_}}
10595            "
10596            .unindent()
10597        );
10598        assert_eq!(
10599            editor.selections.ranges::<Point>(cx),
10600            [
10601                Point::new(0, 4)..Point::new(0, 4),
10602                Point::new(1, 4)..Point::new(1, 4),
10603                Point::new(2, 4)..Point::new(2, 4)
10604            ]
10605        );
10606
10607        editor.backspace(&Default::default(), window, cx);
10608        editor.backspace(&Default::default(), window, cx);
10609        assert_eq!(
10610            editor.text(cx),
10611            "
10612                a{}
10613                b{}
10614                c{}
10615            "
10616            .unindent()
10617        );
10618        assert_eq!(
10619            editor.selections.ranges::<Point>(cx),
10620            [
10621                Point::new(0, 2)..Point::new(0, 2),
10622                Point::new(1, 2)..Point::new(1, 2),
10623                Point::new(2, 2)..Point::new(2, 2)
10624            ]
10625        );
10626
10627        editor.delete_to_previous_word_start(&Default::default(), window, cx);
10628        assert_eq!(
10629            editor.text(cx),
10630            "
10631                a
10632                b
10633                c
10634            "
10635            .unindent()
10636        );
10637        assert_eq!(
10638            editor.selections.ranges::<Point>(cx),
10639            [
10640                Point::new(0, 1)..Point::new(0, 1),
10641                Point::new(1, 1)..Point::new(1, 1),
10642                Point::new(2, 1)..Point::new(2, 1)
10643            ]
10644        );
10645    });
10646}
10647
10648#[gpui::test]
10649async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
10650    init_test(cx, |settings| {
10651        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10652    });
10653
10654    let mut cx = EditorTestContext::new(cx).await;
10655
10656    let language = Arc::new(Language::new(
10657        LanguageConfig {
10658            brackets: BracketPairConfig {
10659                pairs: vec![
10660                    BracketPair {
10661                        start: "{".to_string(),
10662                        end: "}".to_string(),
10663                        close: true,
10664                        surround: true,
10665                        newline: true,
10666                    },
10667                    BracketPair {
10668                        start: "(".to_string(),
10669                        end: ")".to_string(),
10670                        close: true,
10671                        surround: true,
10672                        newline: true,
10673                    },
10674                    BracketPair {
10675                        start: "[".to_string(),
10676                        end: "]".to_string(),
10677                        close: false,
10678                        surround: true,
10679                        newline: true,
10680                    },
10681                ],
10682                ..Default::default()
10683            },
10684            autoclose_before: "})]".to_string(),
10685            ..Default::default()
10686        },
10687        Some(tree_sitter_rust::LANGUAGE.into()),
10688    ));
10689
10690    cx.language_registry().add(language.clone());
10691    cx.update_buffer(|buffer, cx| {
10692        buffer.set_language(Some(language), cx);
10693    });
10694
10695    cx.set_state(
10696        &"
10697            {(ˇ)}
10698            [[ˇ]]
10699            {(ˇ)}
10700        "
10701        .unindent(),
10702    );
10703
10704    cx.update_editor(|editor, window, cx| {
10705        editor.backspace(&Default::default(), window, cx);
10706        editor.backspace(&Default::default(), window, cx);
10707    });
10708
10709    cx.assert_editor_state(
10710        &"
10711            ˇ
10712            ˇ]]
10713            ˇ
10714        "
10715        .unindent(),
10716    );
10717
10718    cx.update_editor(|editor, window, cx| {
10719        editor.handle_input("{", window, cx);
10720        editor.handle_input("{", window, cx);
10721        editor.move_right(&MoveRight, window, cx);
10722        editor.move_right(&MoveRight, window, cx);
10723        editor.move_left(&MoveLeft, window, cx);
10724        editor.move_left(&MoveLeft, window, cx);
10725        editor.backspace(&Default::default(), window, cx);
10726    });
10727
10728    cx.assert_editor_state(
10729        &"
10730            {ˇ}
10731            {ˇ}]]
10732            {ˇ}
10733        "
10734        .unindent(),
10735    );
10736
10737    cx.update_editor(|editor, window, cx| {
10738        editor.backspace(&Default::default(), window, cx);
10739    });
10740
10741    cx.assert_editor_state(
10742        &"
10743            ˇ
10744            ˇ]]
10745            ˇ
10746        "
10747        .unindent(),
10748    );
10749}
10750
10751#[gpui::test]
10752async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
10753    init_test(cx, |_| {});
10754
10755    let language = Arc::new(Language::new(
10756        LanguageConfig::default(),
10757        Some(tree_sitter_rust::LANGUAGE.into()),
10758    ));
10759
10760    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
10761    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10762    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10763    editor
10764        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10765        .await;
10766
10767    editor.update_in(cx, |editor, window, cx| {
10768        editor.set_auto_replace_emoji_shortcode(true);
10769
10770        editor.handle_input("Hello ", window, cx);
10771        editor.handle_input(":wave", window, cx);
10772        assert_eq!(editor.text(cx), "Hello :wave".unindent());
10773
10774        editor.handle_input(":", window, cx);
10775        assert_eq!(editor.text(cx), "Hello 👋".unindent());
10776
10777        editor.handle_input(" :smile", window, cx);
10778        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
10779
10780        editor.handle_input(":", window, cx);
10781        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
10782
10783        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
10784        editor.handle_input(":wave", window, cx);
10785        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
10786
10787        editor.handle_input(":", window, cx);
10788        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
10789
10790        editor.handle_input(":1", window, cx);
10791        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
10792
10793        editor.handle_input(":", window, cx);
10794        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
10795
10796        // Ensure shortcode does not get replaced when it is part of a word
10797        editor.handle_input(" Test:wave", window, cx);
10798        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
10799
10800        editor.handle_input(":", window, cx);
10801        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
10802
10803        editor.set_auto_replace_emoji_shortcode(false);
10804
10805        // Ensure shortcode does not get replaced when auto replace is off
10806        editor.handle_input(" :wave", window, cx);
10807        assert_eq!(
10808            editor.text(cx),
10809            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
10810        );
10811
10812        editor.handle_input(":", window, cx);
10813        assert_eq!(
10814            editor.text(cx),
10815            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
10816        );
10817    });
10818}
10819
10820#[gpui::test]
10821async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
10822    init_test(cx, |_| {});
10823
10824    let (text, insertion_ranges) = marked_text_ranges(
10825        indoc! {"
10826            ˇ
10827        "},
10828        false,
10829    );
10830
10831    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
10832    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10833
10834    _ = editor.update_in(cx, |editor, window, cx| {
10835        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
10836
10837        editor
10838            .insert_snippet(&insertion_ranges, snippet, window, cx)
10839            .unwrap();
10840
10841        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
10842            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
10843            assert_eq!(editor.text(cx), expected_text);
10844            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
10845        }
10846
10847        assert(
10848            editor,
10849            cx,
10850            indoc! {"
10851            type «» =•
10852            "},
10853        );
10854
10855        assert!(editor.context_menu_visible(), "There should be a matches");
10856    });
10857}
10858
10859#[gpui::test]
10860async fn test_snippets(cx: &mut TestAppContext) {
10861    init_test(cx, |_| {});
10862
10863    let mut cx = EditorTestContext::new(cx).await;
10864
10865    cx.set_state(indoc! {"
10866        a.ˇ b
10867        a.ˇ b
10868        a.ˇ b
10869    "});
10870
10871    cx.update_editor(|editor, window, cx| {
10872        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
10873        let insertion_ranges = editor
10874            .selections
10875            .all(cx)
10876            .iter()
10877            .map(|s| s.range())
10878            .collect::<Vec<_>>();
10879        editor
10880            .insert_snippet(&insertion_ranges, snippet, window, cx)
10881            .unwrap();
10882    });
10883
10884    cx.assert_editor_state(indoc! {"
10885        a.f(«oneˇ», two, «threeˇ») b
10886        a.f(«oneˇ», two, «threeˇ») b
10887        a.f(«oneˇ», two, «threeˇ») b
10888    "});
10889
10890    // Can't move earlier than the first tab stop
10891    cx.update_editor(|editor, window, cx| {
10892        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10893    });
10894    cx.assert_editor_state(indoc! {"
10895        a.f(«oneˇ», two, «threeˇ») b
10896        a.f(«oneˇ», two, «threeˇ») b
10897        a.f(«oneˇ», two, «threeˇ») b
10898    "});
10899
10900    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
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    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
10908    cx.assert_editor_state(indoc! {"
10909        a.f(«oneˇ», two, «threeˇ») b
10910        a.f(«oneˇ», two, «threeˇ») b
10911        a.f(«oneˇ», two, «threeˇ») b
10912    "});
10913
10914    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10915    cx.assert_editor_state(indoc! {"
10916        a.f(one, «twoˇ», three) b
10917        a.f(one, «twoˇ», three) b
10918        a.f(one, «twoˇ», three) b
10919    "});
10920    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10921    cx.assert_editor_state(indoc! {"
10922        a.f(one, two, three)ˇ b
10923        a.f(one, two, three)ˇ b
10924        a.f(one, two, three)ˇ b
10925    "});
10926
10927    // As soon as the last tab stop is reached, snippet state is gone
10928    cx.update_editor(|editor, window, cx| {
10929        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10930    });
10931    cx.assert_editor_state(indoc! {"
10932        a.f(one, two, three)ˇ b
10933        a.f(one, two, three)ˇ b
10934        a.f(one, two, three)ˇ b
10935    "});
10936}
10937
10938#[gpui::test]
10939async fn test_snippet_indentation(cx: &mut TestAppContext) {
10940    init_test(cx, |_| {});
10941
10942    let mut cx = EditorTestContext::new(cx).await;
10943
10944    cx.update_editor(|editor, window, cx| {
10945        let snippet = Snippet::parse(indoc! {"
10946            /*
10947             * Multiline comment with leading indentation
10948             *
10949             * $1
10950             */
10951            $0"})
10952        .unwrap();
10953        let insertion_ranges = editor
10954            .selections
10955            .all(cx)
10956            .iter()
10957            .map(|s| s.range())
10958            .collect::<Vec<_>>();
10959        editor
10960            .insert_snippet(&insertion_ranges, snippet, window, cx)
10961            .unwrap();
10962    });
10963
10964    cx.assert_editor_state(indoc! {"
10965        /*
10966         * Multiline comment with leading indentation
10967         *
10968         * ˇ
10969         */
10970    "});
10971
10972    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10973    cx.assert_editor_state(indoc! {"
10974        /*
10975         * Multiline comment with leading indentation
10976         *
10977         *•
10978         */
10979        ˇ"});
10980}
10981
10982#[gpui::test]
10983async fn test_document_format_during_save(cx: &mut TestAppContext) {
10984    init_test(cx, |_| {});
10985
10986    let fs = FakeFs::new(cx.executor());
10987    fs.insert_file(path!("/file.rs"), Default::default()).await;
10988
10989    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
10990
10991    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10992    language_registry.add(rust_lang());
10993    let mut fake_servers = language_registry.register_fake_lsp(
10994        "Rust",
10995        FakeLspAdapter {
10996            capabilities: lsp::ServerCapabilities {
10997                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10998                ..Default::default()
10999            },
11000            ..Default::default()
11001        },
11002    );
11003
11004    let buffer = project
11005        .update(cx, |project, cx| {
11006            project.open_local_buffer(path!("/file.rs"), cx)
11007        })
11008        .await
11009        .unwrap();
11010
11011    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11012    let (editor, cx) = cx.add_window_view(|window, cx| {
11013        build_editor_with_project(project.clone(), buffer, window, cx)
11014    });
11015    editor.update_in(cx, |editor, window, cx| {
11016        editor.set_text("one\ntwo\nthree\n", window, cx)
11017    });
11018    assert!(cx.read(|cx| editor.is_dirty(cx)));
11019
11020    cx.executor().start_waiting();
11021    let fake_server = fake_servers.next().await.unwrap();
11022
11023    {
11024        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11025            move |params, _| async move {
11026                assert_eq!(
11027                    params.text_document.uri,
11028                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11029                );
11030                assert_eq!(params.options.tab_size, 4);
11031                Ok(Some(vec![lsp::TextEdit::new(
11032                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11033                    ", ".to_string(),
11034                )]))
11035            },
11036        );
11037        let save = editor
11038            .update_in(cx, |editor, window, cx| {
11039                editor.save(
11040                    SaveOptions {
11041                        format: true,
11042                        autosave: false,
11043                    },
11044                    project.clone(),
11045                    window,
11046                    cx,
11047                )
11048            })
11049            .unwrap();
11050        cx.executor().start_waiting();
11051        save.await;
11052
11053        assert_eq!(
11054            editor.update(cx, |editor, cx| editor.text(cx)),
11055            "one, two\nthree\n"
11056        );
11057        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11058    }
11059
11060    {
11061        editor.update_in(cx, |editor, window, cx| {
11062            editor.set_text("one\ntwo\nthree\n", window, cx)
11063        });
11064        assert!(cx.read(|cx| editor.is_dirty(cx)));
11065
11066        // Ensure we can still save even if formatting hangs.
11067        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11068            move |params, _| async move {
11069                assert_eq!(
11070                    params.text_document.uri,
11071                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11072                );
11073                futures::future::pending::<()>().await;
11074                unreachable!()
11075            },
11076        );
11077        let save = editor
11078            .update_in(cx, |editor, window, cx| {
11079                editor.save(
11080                    SaveOptions {
11081                        format: true,
11082                        autosave: false,
11083                    },
11084                    project.clone(),
11085                    window,
11086                    cx,
11087                )
11088            })
11089            .unwrap();
11090        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11091        cx.executor().start_waiting();
11092        save.await;
11093        assert_eq!(
11094            editor.update(cx, |editor, cx| editor.text(cx)),
11095            "one\ntwo\nthree\n"
11096        );
11097    }
11098
11099    // Set rust language override and assert overridden tabsize is sent to language server
11100    update_test_language_settings(cx, |settings| {
11101        settings.languages.0.insert(
11102            "Rust".into(),
11103            LanguageSettingsContent {
11104                tab_size: NonZeroU32::new(8),
11105                ..Default::default()
11106            },
11107        );
11108    });
11109
11110    {
11111        editor.update_in(cx, |editor, window, cx| {
11112            editor.set_text("somehting_new\n", window, cx)
11113        });
11114        assert!(cx.read(|cx| editor.is_dirty(cx)));
11115        let _formatting_request_signal = fake_server
11116            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11117                assert_eq!(
11118                    params.text_document.uri,
11119                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11120                );
11121                assert_eq!(params.options.tab_size, 8);
11122                Ok(Some(vec![]))
11123            });
11124        let save = editor
11125            .update_in(cx, |editor, window, cx| {
11126                editor.save(
11127                    SaveOptions {
11128                        format: true,
11129                        autosave: false,
11130                    },
11131                    project.clone(),
11132                    window,
11133                    cx,
11134                )
11135            })
11136            .unwrap();
11137        cx.executor().start_waiting();
11138        save.await;
11139    }
11140}
11141
11142#[gpui::test]
11143async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11144    init_test(cx, |settings| {
11145        settings.defaults.ensure_final_newline_on_save = Some(false);
11146    });
11147
11148    let fs = FakeFs::new(cx.executor());
11149    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11150
11151    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11152
11153    let buffer = project
11154        .update(cx, |project, cx| {
11155            project.open_local_buffer(path!("/file.txt"), cx)
11156        })
11157        .await
11158        .unwrap();
11159
11160    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11161    let (editor, cx) = cx.add_window_view(|window, cx| {
11162        build_editor_with_project(project.clone(), buffer, window, cx)
11163    });
11164    editor.update_in(cx, |editor, window, cx| {
11165        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11166            s.select_ranges([0..0])
11167        });
11168    });
11169    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11170
11171    editor.update_in(cx, |editor, window, cx| {
11172        editor.handle_input("\n", window, cx)
11173    });
11174    cx.run_until_parked();
11175    save(&editor, &project, cx).await;
11176    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11177
11178    editor.update_in(cx, |editor, window, cx| {
11179        editor.undo(&Default::default(), window, cx);
11180    });
11181    save(&editor, &project, cx).await;
11182    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11183
11184    editor.update_in(cx, |editor, window, cx| {
11185        editor.redo(&Default::default(), window, cx);
11186    });
11187    cx.run_until_parked();
11188    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11189
11190    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11191        let save = editor
11192            .update_in(cx, |editor, window, cx| {
11193                editor.save(
11194                    SaveOptions {
11195                        format: true,
11196                        autosave: false,
11197                    },
11198                    project.clone(),
11199                    window,
11200                    cx,
11201                )
11202            })
11203            .unwrap();
11204        cx.executor().start_waiting();
11205        save.await;
11206        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11207    }
11208}
11209
11210#[gpui::test]
11211async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
11212    init_test(cx, |_| {});
11213
11214    let cols = 4;
11215    let rows = 10;
11216    let sample_text_1 = sample_text(rows, cols, 'a');
11217    assert_eq!(
11218        sample_text_1,
11219        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11220    );
11221    let sample_text_2 = sample_text(rows, cols, 'l');
11222    assert_eq!(
11223        sample_text_2,
11224        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11225    );
11226    let sample_text_3 = sample_text(rows, cols, 'v');
11227    assert_eq!(
11228        sample_text_3,
11229        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11230    );
11231
11232    let fs = FakeFs::new(cx.executor());
11233    fs.insert_tree(
11234        path!("/a"),
11235        json!({
11236            "main.rs": sample_text_1,
11237            "other.rs": sample_text_2,
11238            "lib.rs": sample_text_3,
11239        }),
11240    )
11241    .await;
11242
11243    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11244    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11245    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11246
11247    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11248    language_registry.add(rust_lang());
11249    let mut fake_servers = language_registry.register_fake_lsp(
11250        "Rust",
11251        FakeLspAdapter {
11252            capabilities: lsp::ServerCapabilities {
11253                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11254                ..Default::default()
11255            },
11256            ..Default::default()
11257        },
11258    );
11259
11260    let worktree = project.update(cx, |project, cx| {
11261        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
11262        assert_eq!(worktrees.len(), 1);
11263        worktrees.pop().unwrap()
11264    });
11265    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11266
11267    let buffer_1 = project
11268        .update(cx, |project, cx| {
11269            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
11270        })
11271        .await
11272        .unwrap();
11273    let buffer_2 = project
11274        .update(cx, |project, cx| {
11275            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
11276        })
11277        .await
11278        .unwrap();
11279    let buffer_3 = project
11280        .update(cx, |project, cx| {
11281            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
11282        })
11283        .await
11284        .unwrap();
11285
11286    let multi_buffer = cx.new(|cx| {
11287        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11288        multi_buffer.push_excerpts(
11289            buffer_1.clone(),
11290            [
11291                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11292                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11293                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11294            ],
11295            cx,
11296        );
11297        multi_buffer.push_excerpts(
11298            buffer_2.clone(),
11299            [
11300                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11301                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11302                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11303            ],
11304            cx,
11305        );
11306        multi_buffer.push_excerpts(
11307            buffer_3.clone(),
11308            [
11309                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11310                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11311                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11312            ],
11313            cx,
11314        );
11315        multi_buffer
11316    });
11317    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
11318        Editor::new(
11319            EditorMode::full(),
11320            multi_buffer,
11321            Some(project.clone()),
11322            window,
11323            cx,
11324        )
11325    });
11326
11327    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11328        editor.change_selections(
11329            SelectionEffects::scroll(Autoscroll::Next),
11330            window,
11331            cx,
11332            |s| s.select_ranges(Some(1..2)),
11333        );
11334        editor.insert("|one|two|three|", window, cx);
11335    });
11336    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11337    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11338        editor.change_selections(
11339            SelectionEffects::scroll(Autoscroll::Next),
11340            window,
11341            cx,
11342            |s| s.select_ranges(Some(60..70)),
11343        );
11344        editor.insert("|four|five|six|", window, cx);
11345    });
11346    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11347
11348    // First two buffers should be edited, but not the third one.
11349    assert_eq!(
11350        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11351        "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}",
11352    );
11353    buffer_1.update(cx, |buffer, _| {
11354        assert!(buffer.is_dirty());
11355        assert_eq!(
11356            buffer.text(),
11357            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
11358        )
11359    });
11360    buffer_2.update(cx, |buffer, _| {
11361        assert!(buffer.is_dirty());
11362        assert_eq!(
11363            buffer.text(),
11364            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
11365        )
11366    });
11367    buffer_3.update(cx, |buffer, _| {
11368        assert!(!buffer.is_dirty());
11369        assert_eq!(buffer.text(), sample_text_3,)
11370    });
11371    cx.executor().run_until_parked();
11372
11373    cx.executor().start_waiting();
11374    let save = multi_buffer_editor
11375        .update_in(cx, |editor, window, cx| {
11376            editor.save(
11377                SaveOptions {
11378                    format: true,
11379                    autosave: false,
11380                },
11381                project.clone(),
11382                window,
11383                cx,
11384            )
11385        })
11386        .unwrap();
11387
11388    let fake_server = fake_servers.next().await.unwrap();
11389    fake_server
11390        .server
11391        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11392            Ok(Some(vec![lsp::TextEdit::new(
11393                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11394                format!("[{} formatted]", params.text_document.uri),
11395            )]))
11396        })
11397        .detach();
11398    save.await;
11399
11400    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11401    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11402    assert_eq!(
11403        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11404        uri!(
11405            "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}"
11406        ),
11407    );
11408    buffer_1.update(cx, |buffer, _| {
11409        assert!(!buffer.is_dirty());
11410        assert_eq!(
11411            buffer.text(),
11412            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11413        )
11414    });
11415    buffer_2.update(cx, |buffer, _| {
11416        assert!(!buffer.is_dirty());
11417        assert_eq!(
11418            buffer.text(),
11419            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11420        )
11421    });
11422    buffer_3.update(cx, |buffer, _| {
11423        assert!(!buffer.is_dirty());
11424        assert_eq!(buffer.text(), sample_text_3,)
11425    });
11426}
11427
11428#[gpui::test]
11429async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
11430    init_test(cx, |_| {});
11431
11432    let fs = FakeFs::new(cx.executor());
11433    fs.insert_tree(
11434        path!("/dir"),
11435        json!({
11436            "file1.rs": "fn main() { println!(\"hello\"); }",
11437            "file2.rs": "fn test() { println!(\"test\"); }",
11438            "file3.rs": "fn other() { println!(\"other\"); }\n",
11439        }),
11440    )
11441    .await;
11442
11443    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
11444    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11445    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11446
11447    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11448    language_registry.add(rust_lang());
11449
11450    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
11451    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11452
11453    // Open three buffers
11454    let buffer_1 = project
11455        .update(cx, |project, cx| {
11456            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
11457        })
11458        .await
11459        .unwrap();
11460    let buffer_2 = project
11461        .update(cx, |project, cx| {
11462            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
11463        })
11464        .await
11465        .unwrap();
11466    let buffer_3 = project
11467        .update(cx, |project, cx| {
11468            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
11469        })
11470        .await
11471        .unwrap();
11472
11473    // Create a multi-buffer with all three buffers
11474    let multi_buffer = cx.new(|cx| {
11475        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11476        multi_buffer.push_excerpts(
11477            buffer_1.clone(),
11478            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11479            cx,
11480        );
11481        multi_buffer.push_excerpts(
11482            buffer_2.clone(),
11483            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11484            cx,
11485        );
11486        multi_buffer.push_excerpts(
11487            buffer_3.clone(),
11488            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11489            cx,
11490        );
11491        multi_buffer
11492    });
11493
11494    let editor = cx.new_window_entity(|window, cx| {
11495        Editor::new(
11496            EditorMode::full(),
11497            multi_buffer,
11498            Some(project.clone()),
11499            window,
11500            cx,
11501        )
11502    });
11503
11504    // Edit only the first buffer
11505    editor.update_in(cx, |editor, window, cx| {
11506        editor.change_selections(
11507            SelectionEffects::scroll(Autoscroll::Next),
11508            window,
11509            cx,
11510            |s| s.select_ranges(Some(10..10)),
11511        );
11512        editor.insert("// edited", window, cx);
11513    });
11514
11515    // Verify that only buffer 1 is dirty
11516    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
11517    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11518    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11519
11520    // Get write counts after file creation (files were created with initial content)
11521    // We expect each file to have been written once during creation
11522    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
11523    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
11524    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
11525
11526    // Perform autosave
11527    let save_task = editor.update_in(cx, |editor, window, cx| {
11528        editor.save(
11529            SaveOptions {
11530                format: true,
11531                autosave: true,
11532            },
11533            project.clone(),
11534            window,
11535            cx,
11536        )
11537    });
11538    save_task.await.unwrap();
11539
11540    // Only the dirty buffer should have been saved
11541    assert_eq!(
11542        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11543        1,
11544        "Buffer 1 was dirty, so it should have been written once during autosave"
11545    );
11546    assert_eq!(
11547        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11548        0,
11549        "Buffer 2 was clean, so it should not have been written during autosave"
11550    );
11551    assert_eq!(
11552        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11553        0,
11554        "Buffer 3 was clean, so it should not have been written during autosave"
11555    );
11556
11557    // Verify buffer states after autosave
11558    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11559    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11560    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11561
11562    // Now perform a manual save (format = true)
11563    let save_task = editor.update_in(cx, |editor, window, cx| {
11564        editor.save(
11565            SaveOptions {
11566                format: true,
11567                autosave: false,
11568            },
11569            project.clone(),
11570            window,
11571            cx,
11572        )
11573    });
11574    save_task.await.unwrap();
11575
11576    // During manual save, clean buffers don't get written to disk
11577    // They just get did_save called for language server notifications
11578    assert_eq!(
11579        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11580        1,
11581        "Buffer 1 should only have been written once total (during autosave, not manual save)"
11582    );
11583    assert_eq!(
11584        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11585        0,
11586        "Buffer 2 should not have been written at all"
11587    );
11588    assert_eq!(
11589        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11590        0,
11591        "Buffer 3 should not have been written at all"
11592    );
11593}
11594
11595async fn setup_range_format_test(
11596    cx: &mut TestAppContext,
11597) -> (
11598    Entity<Project>,
11599    Entity<Editor>,
11600    &mut gpui::VisualTestContext,
11601    lsp::FakeLanguageServer,
11602) {
11603    init_test(cx, |_| {});
11604
11605    let fs = FakeFs::new(cx.executor());
11606    fs.insert_file(path!("/file.rs"), Default::default()).await;
11607
11608    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11609
11610    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11611    language_registry.add(rust_lang());
11612    let mut fake_servers = language_registry.register_fake_lsp(
11613        "Rust",
11614        FakeLspAdapter {
11615            capabilities: lsp::ServerCapabilities {
11616                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
11617                ..lsp::ServerCapabilities::default()
11618            },
11619            ..FakeLspAdapter::default()
11620        },
11621    );
11622
11623    let buffer = project
11624        .update(cx, |project, cx| {
11625            project.open_local_buffer(path!("/file.rs"), cx)
11626        })
11627        .await
11628        .unwrap();
11629
11630    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11631    let (editor, cx) = cx.add_window_view(|window, cx| {
11632        build_editor_with_project(project.clone(), buffer, window, cx)
11633    });
11634
11635    cx.executor().start_waiting();
11636    let fake_server = fake_servers.next().await.unwrap();
11637
11638    (project, editor, cx, fake_server)
11639}
11640
11641#[gpui::test]
11642async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
11643    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11644
11645    editor.update_in(cx, |editor, window, cx| {
11646        editor.set_text("one\ntwo\nthree\n", window, cx)
11647    });
11648    assert!(cx.read(|cx| editor.is_dirty(cx)));
11649
11650    let save = editor
11651        .update_in(cx, |editor, window, cx| {
11652            editor.save(
11653                SaveOptions {
11654                    format: true,
11655                    autosave: false,
11656                },
11657                project.clone(),
11658                window,
11659                cx,
11660            )
11661        })
11662        .unwrap();
11663    fake_server
11664        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11665            assert_eq!(
11666                params.text_document.uri,
11667                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11668            );
11669            assert_eq!(params.options.tab_size, 4);
11670            Ok(Some(vec![lsp::TextEdit::new(
11671                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11672                ", ".to_string(),
11673            )]))
11674        })
11675        .next()
11676        .await;
11677    cx.executor().start_waiting();
11678    save.await;
11679    assert_eq!(
11680        editor.update(cx, |editor, cx| editor.text(cx)),
11681        "one, two\nthree\n"
11682    );
11683    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11684}
11685
11686#[gpui::test]
11687async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
11688    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11689
11690    editor.update_in(cx, |editor, window, cx| {
11691        editor.set_text("one\ntwo\nthree\n", window, cx)
11692    });
11693    assert!(cx.read(|cx| editor.is_dirty(cx)));
11694
11695    // Test that save still works when formatting hangs
11696    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
11697        move |params, _| async move {
11698            assert_eq!(
11699                params.text_document.uri,
11700                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11701            );
11702            futures::future::pending::<()>().await;
11703            unreachable!()
11704        },
11705    );
11706    let save = editor
11707        .update_in(cx, |editor, window, cx| {
11708            editor.save(
11709                SaveOptions {
11710                    format: true,
11711                    autosave: false,
11712                },
11713                project.clone(),
11714                window,
11715                cx,
11716            )
11717        })
11718        .unwrap();
11719    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11720    cx.executor().start_waiting();
11721    save.await;
11722    assert_eq!(
11723        editor.update(cx, |editor, cx| editor.text(cx)),
11724        "one\ntwo\nthree\n"
11725    );
11726    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11727}
11728
11729#[gpui::test]
11730async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
11731    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11732
11733    // Buffer starts clean, no formatting should be requested
11734    let save = editor
11735        .update_in(cx, |editor, window, cx| {
11736            editor.save(
11737                SaveOptions {
11738                    format: false,
11739                    autosave: false,
11740                },
11741                project.clone(),
11742                window,
11743                cx,
11744            )
11745        })
11746        .unwrap();
11747    let _pending_format_request = fake_server
11748        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
11749            panic!("Should not be invoked");
11750        })
11751        .next();
11752    cx.executor().start_waiting();
11753    save.await;
11754    cx.run_until_parked();
11755}
11756
11757#[gpui::test]
11758async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
11759    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11760
11761    // Set Rust language override and assert overridden tabsize is sent to language server
11762    update_test_language_settings(cx, |settings| {
11763        settings.languages.0.insert(
11764            "Rust".into(),
11765            LanguageSettingsContent {
11766                tab_size: NonZeroU32::new(8),
11767                ..Default::default()
11768            },
11769        );
11770    });
11771
11772    editor.update_in(cx, |editor, window, cx| {
11773        editor.set_text("something_new\n", window, cx)
11774    });
11775    assert!(cx.read(|cx| editor.is_dirty(cx)));
11776    let save = editor
11777        .update_in(cx, |editor, window, cx| {
11778            editor.save(
11779                SaveOptions {
11780                    format: true,
11781                    autosave: false,
11782                },
11783                project.clone(),
11784                window,
11785                cx,
11786            )
11787        })
11788        .unwrap();
11789    fake_server
11790        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11791            assert_eq!(
11792                params.text_document.uri,
11793                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11794            );
11795            assert_eq!(params.options.tab_size, 8);
11796            Ok(Some(Vec::new()))
11797        })
11798        .next()
11799        .await;
11800    save.await;
11801}
11802
11803#[gpui::test]
11804async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
11805    init_test(cx, |settings| {
11806        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
11807            Formatter::LanguageServer { name: None },
11808        )))
11809    });
11810
11811    let fs = FakeFs::new(cx.executor());
11812    fs.insert_file(path!("/file.rs"), Default::default()).await;
11813
11814    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11815
11816    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11817    language_registry.add(Arc::new(Language::new(
11818        LanguageConfig {
11819            name: "Rust".into(),
11820            matcher: LanguageMatcher {
11821                path_suffixes: vec!["rs".to_string()],
11822                ..Default::default()
11823            },
11824            ..LanguageConfig::default()
11825        },
11826        Some(tree_sitter_rust::LANGUAGE.into()),
11827    )));
11828    update_test_language_settings(cx, |settings| {
11829        // Enable Prettier formatting for the same buffer, and ensure
11830        // LSP is called instead of Prettier.
11831        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
11832    });
11833    let mut fake_servers = language_registry.register_fake_lsp(
11834        "Rust",
11835        FakeLspAdapter {
11836            capabilities: lsp::ServerCapabilities {
11837                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11838                ..Default::default()
11839            },
11840            ..Default::default()
11841        },
11842    );
11843
11844    let buffer = project
11845        .update(cx, |project, cx| {
11846            project.open_local_buffer(path!("/file.rs"), cx)
11847        })
11848        .await
11849        .unwrap();
11850
11851    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11852    let (editor, cx) = cx.add_window_view(|window, cx| {
11853        build_editor_with_project(project.clone(), buffer, window, cx)
11854    });
11855    editor.update_in(cx, |editor, window, cx| {
11856        editor.set_text("one\ntwo\nthree\n", window, cx)
11857    });
11858
11859    cx.executor().start_waiting();
11860    let fake_server = fake_servers.next().await.unwrap();
11861
11862    let format = editor
11863        .update_in(cx, |editor, window, cx| {
11864            editor.perform_format(
11865                project.clone(),
11866                FormatTrigger::Manual,
11867                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11868                window,
11869                cx,
11870            )
11871        })
11872        .unwrap();
11873    fake_server
11874        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11875            assert_eq!(
11876                params.text_document.uri,
11877                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11878            );
11879            assert_eq!(params.options.tab_size, 4);
11880            Ok(Some(vec![lsp::TextEdit::new(
11881                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11882                ", ".to_string(),
11883            )]))
11884        })
11885        .next()
11886        .await;
11887    cx.executor().start_waiting();
11888    format.await;
11889    assert_eq!(
11890        editor.update(cx, |editor, cx| editor.text(cx)),
11891        "one, two\nthree\n"
11892    );
11893
11894    editor.update_in(cx, |editor, window, cx| {
11895        editor.set_text("one\ntwo\nthree\n", window, cx)
11896    });
11897    // Ensure we don't lock if formatting hangs.
11898    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11899        move |params, _| async move {
11900            assert_eq!(
11901                params.text_document.uri,
11902                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11903            );
11904            futures::future::pending::<()>().await;
11905            unreachable!()
11906        },
11907    );
11908    let format = editor
11909        .update_in(cx, |editor, window, cx| {
11910            editor.perform_format(
11911                project,
11912                FormatTrigger::Manual,
11913                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11914                window,
11915                cx,
11916            )
11917        })
11918        .unwrap();
11919    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11920    cx.executor().start_waiting();
11921    format.await;
11922    assert_eq!(
11923        editor.update(cx, |editor, cx| editor.text(cx)),
11924        "one\ntwo\nthree\n"
11925    );
11926}
11927
11928#[gpui::test]
11929async fn test_multiple_formatters(cx: &mut TestAppContext) {
11930    init_test(cx, |settings| {
11931        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
11932        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
11933            Formatter::LanguageServer { name: None },
11934            Formatter::CodeAction("code-action-1".into()),
11935            Formatter::CodeAction("code-action-2".into()),
11936        ])))
11937    });
11938
11939    let fs = FakeFs::new(cx.executor());
11940    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
11941        .await;
11942
11943    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11944    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11945    language_registry.add(rust_lang());
11946
11947    let mut fake_servers = language_registry.register_fake_lsp(
11948        "Rust",
11949        FakeLspAdapter {
11950            capabilities: lsp::ServerCapabilities {
11951                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11952                execute_command_provider: Some(lsp::ExecuteCommandOptions {
11953                    commands: vec!["the-command-for-code-action-1".into()],
11954                    ..Default::default()
11955                }),
11956                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
11957                ..Default::default()
11958            },
11959            ..Default::default()
11960        },
11961    );
11962
11963    let buffer = project
11964        .update(cx, |project, cx| {
11965            project.open_local_buffer(path!("/file.rs"), cx)
11966        })
11967        .await
11968        .unwrap();
11969
11970    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11971    let (editor, cx) = cx.add_window_view(|window, cx| {
11972        build_editor_with_project(project.clone(), buffer, window, cx)
11973    });
11974
11975    cx.executor().start_waiting();
11976
11977    let fake_server = fake_servers.next().await.unwrap();
11978    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11979        move |_params, _| async move {
11980            Ok(Some(vec![lsp::TextEdit::new(
11981                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11982                "applied-formatting\n".to_string(),
11983            )]))
11984        },
11985    );
11986    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
11987        move |params, _| async move {
11988            let requested_code_actions = params.context.only.expect("Expected code action request");
11989            assert_eq!(requested_code_actions.len(), 1);
11990
11991            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
11992            let code_action = match requested_code_actions[0].as_str() {
11993                "code-action-1" => lsp::CodeAction {
11994                    kind: Some("code-action-1".into()),
11995                    edit: Some(lsp::WorkspaceEdit::new(
11996                        [(
11997                            uri,
11998                            vec![lsp::TextEdit::new(
11999                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12000                                "applied-code-action-1-edit\n".to_string(),
12001                            )],
12002                        )]
12003                        .into_iter()
12004                        .collect(),
12005                    )),
12006                    command: Some(lsp::Command {
12007                        command: "the-command-for-code-action-1".into(),
12008                        ..Default::default()
12009                    }),
12010                    ..Default::default()
12011                },
12012                "code-action-2" => lsp::CodeAction {
12013                    kind: Some("code-action-2".into()),
12014                    edit: Some(lsp::WorkspaceEdit::new(
12015                        [(
12016                            uri,
12017                            vec![lsp::TextEdit::new(
12018                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12019                                "applied-code-action-2-edit\n".to_string(),
12020                            )],
12021                        )]
12022                        .into_iter()
12023                        .collect(),
12024                    )),
12025                    ..Default::default()
12026                },
12027                req => panic!("Unexpected code action request: {:?}", req),
12028            };
12029            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12030                code_action,
12031            )]))
12032        },
12033    );
12034
12035    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
12036        move |params, _| async move { Ok(params) }
12037    });
12038
12039    let command_lock = Arc::new(futures::lock::Mutex::new(()));
12040    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
12041        let fake = fake_server.clone();
12042        let lock = command_lock.clone();
12043        move |params, _| {
12044            assert_eq!(params.command, "the-command-for-code-action-1");
12045            let fake = fake.clone();
12046            let lock = lock.clone();
12047            async move {
12048                lock.lock().await;
12049                fake.server
12050                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
12051                        label: None,
12052                        edit: lsp::WorkspaceEdit {
12053                            changes: Some(
12054                                [(
12055                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
12056                                    vec![lsp::TextEdit {
12057                                        range: lsp::Range::new(
12058                                            lsp::Position::new(0, 0),
12059                                            lsp::Position::new(0, 0),
12060                                        ),
12061                                        new_text: "applied-code-action-1-command\n".into(),
12062                                    }],
12063                                )]
12064                                .into_iter()
12065                                .collect(),
12066                            ),
12067                            ..Default::default()
12068                        },
12069                    })
12070                    .await
12071                    .into_response()
12072                    .unwrap();
12073                Ok(Some(json!(null)))
12074            }
12075        }
12076    });
12077
12078    cx.executor().start_waiting();
12079    editor
12080        .update_in(cx, |editor, window, cx| {
12081            editor.perform_format(
12082                project.clone(),
12083                FormatTrigger::Manual,
12084                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12085                window,
12086                cx,
12087            )
12088        })
12089        .unwrap()
12090        .await;
12091    editor.update(cx, |editor, cx| {
12092        assert_eq!(
12093            editor.text(cx),
12094            r#"
12095                applied-code-action-2-edit
12096                applied-code-action-1-command
12097                applied-code-action-1-edit
12098                applied-formatting
12099                one
12100                two
12101                three
12102            "#
12103            .unindent()
12104        );
12105    });
12106
12107    editor.update_in(cx, |editor, window, cx| {
12108        editor.undo(&Default::default(), window, cx);
12109        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12110    });
12111
12112    // Perform a manual edit while waiting for an LSP command
12113    // that's being run as part of a formatting code action.
12114    let lock_guard = command_lock.lock().await;
12115    let format = editor
12116        .update_in(cx, |editor, window, cx| {
12117            editor.perform_format(
12118                project.clone(),
12119                FormatTrigger::Manual,
12120                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12121                window,
12122                cx,
12123            )
12124        })
12125        .unwrap();
12126    cx.run_until_parked();
12127    editor.update(cx, |editor, cx| {
12128        assert_eq!(
12129            editor.text(cx),
12130            r#"
12131                applied-code-action-1-edit
12132                applied-formatting
12133                one
12134                two
12135                three
12136            "#
12137            .unindent()
12138        );
12139
12140        editor.buffer.update(cx, |buffer, cx| {
12141            let ix = buffer.len(cx);
12142            buffer.edit([(ix..ix, "edited\n")], None, cx);
12143        });
12144    });
12145
12146    // Allow the LSP command to proceed. Because the buffer was edited,
12147    // the second code action will not be run.
12148    drop(lock_guard);
12149    format.await;
12150    editor.update_in(cx, |editor, window, cx| {
12151        assert_eq!(
12152            editor.text(cx),
12153            r#"
12154                applied-code-action-1-command
12155                applied-code-action-1-edit
12156                applied-formatting
12157                one
12158                two
12159                three
12160                edited
12161            "#
12162            .unindent()
12163        );
12164
12165        // The manual edit is undone first, because it is the last thing the user did
12166        // (even though the command completed afterwards).
12167        editor.undo(&Default::default(), 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            "#
12178            .unindent()
12179        );
12180
12181        // All the formatting (including the command, which completed after the manual edit)
12182        // is undone together.
12183        editor.undo(&Default::default(), window, cx);
12184        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12185    });
12186}
12187
12188#[gpui::test]
12189async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12190    init_test(cx, |settings| {
12191        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
12192            Formatter::LanguageServer { name: None },
12193        ])))
12194    });
12195
12196    let fs = FakeFs::new(cx.executor());
12197    fs.insert_file(path!("/file.ts"), Default::default()).await;
12198
12199    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12200
12201    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12202    language_registry.add(Arc::new(Language::new(
12203        LanguageConfig {
12204            name: "TypeScript".into(),
12205            matcher: LanguageMatcher {
12206                path_suffixes: vec!["ts".to_string()],
12207                ..Default::default()
12208            },
12209            ..LanguageConfig::default()
12210        },
12211        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12212    )));
12213    update_test_language_settings(cx, |settings| {
12214        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12215    });
12216    let mut fake_servers = language_registry.register_fake_lsp(
12217        "TypeScript",
12218        FakeLspAdapter {
12219            capabilities: lsp::ServerCapabilities {
12220                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12221                ..Default::default()
12222            },
12223            ..Default::default()
12224        },
12225    );
12226
12227    let buffer = project
12228        .update(cx, |project, cx| {
12229            project.open_local_buffer(path!("/file.ts"), cx)
12230        })
12231        .await
12232        .unwrap();
12233
12234    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12235    let (editor, cx) = cx.add_window_view(|window, cx| {
12236        build_editor_with_project(project.clone(), buffer, window, cx)
12237    });
12238    editor.update_in(cx, |editor, window, cx| {
12239        editor.set_text(
12240            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12241            window,
12242            cx,
12243        )
12244    });
12245
12246    cx.executor().start_waiting();
12247    let fake_server = fake_servers.next().await.unwrap();
12248
12249    let format = editor
12250        .update_in(cx, |editor, window, cx| {
12251            editor.perform_code_action_kind(
12252                project.clone(),
12253                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12254                window,
12255                cx,
12256            )
12257        })
12258        .unwrap();
12259    fake_server
12260        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12261            assert_eq!(
12262                params.text_document.uri,
12263                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12264            );
12265            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12266                lsp::CodeAction {
12267                    title: "Organize Imports".to_string(),
12268                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12269                    edit: Some(lsp::WorkspaceEdit {
12270                        changes: Some(
12271                            [(
12272                                params.text_document.uri.clone(),
12273                                vec![lsp::TextEdit::new(
12274                                    lsp::Range::new(
12275                                        lsp::Position::new(1, 0),
12276                                        lsp::Position::new(2, 0),
12277                                    ),
12278                                    "".to_string(),
12279                                )],
12280                            )]
12281                            .into_iter()
12282                            .collect(),
12283                        ),
12284                        ..Default::default()
12285                    }),
12286                    ..Default::default()
12287                },
12288            )]))
12289        })
12290        .next()
12291        .await;
12292    cx.executor().start_waiting();
12293    format.await;
12294    assert_eq!(
12295        editor.update(cx, |editor, cx| editor.text(cx)),
12296        "import { a } from 'module';\n\nconst x = a;\n"
12297    );
12298
12299    editor.update_in(cx, |editor, window, cx| {
12300        editor.set_text(
12301            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12302            window,
12303            cx,
12304        )
12305    });
12306    // Ensure we don't lock if code action hangs.
12307    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12308        move |params, _| async move {
12309            assert_eq!(
12310                params.text_document.uri,
12311                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12312            );
12313            futures::future::pending::<()>().await;
12314            unreachable!()
12315        },
12316    );
12317    let format = editor
12318        .update_in(cx, |editor, window, cx| {
12319            editor.perform_code_action_kind(
12320                project,
12321                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12322                window,
12323                cx,
12324            )
12325        })
12326        .unwrap();
12327    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
12328    cx.executor().start_waiting();
12329    format.await;
12330    assert_eq!(
12331        editor.update(cx, |editor, cx| editor.text(cx)),
12332        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
12333    );
12334}
12335
12336#[gpui::test]
12337async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
12338    init_test(cx, |_| {});
12339
12340    let mut cx = EditorLspTestContext::new_rust(
12341        lsp::ServerCapabilities {
12342            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12343            ..Default::default()
12344        },
12345        cx,
12346    )
12347    .await;
12348
12349    cx.set_state(indoc! {"
12350        one.twoˇ
12351    "});
12352
12353    // The format request takes a long time. When it completes, it inserts
12354    // a newline and an indent before the `.`
12355    cx.lsp
12356        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12357            let executor = cx.background_executor().clone();
12358            async move {
12359                executor.timer(Duration::from_millis(100)).await;
12360                Ok(Some(vec![lsp::TextEdit {
12361                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12362                    new_text: "\n    ".into(),
12363                }]))
12364            }
12365        });
12366
12367    // Submit a format request.
12368    let format_1 = cx
12369        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12370        .unwrap();
12371    cx.executor().run_until_parked();
12372
12373    // Submit a second format request.
12374    let format_2 = cx
12375        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12376        .unwrap();
12377    cx.executor().run_until_parked();
12378
12379    // Wait for both format requests to complete
12380    cx.executor().advance_clock(Duration::from_millis(200));
12381    cx.executor().start_waiting();
12382    format_1.await.unwrap();
12383    cx.executor().start_waiting();
12384    format_2.await.unwrap();
12385
12386    // The formatting edits only happens once.
12387    cx.assert_editor_state(indoc! {"
12388        one
12389            .twoˇ
12390    "});
12391}
12392
12393#[gpui::test]
12394async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12395    init_test(cx, |settings| {
12396        settings.defaults.formatter = Some(SelectedFormatter::Auto)
12397    });
12398
12399    let mut cx = EditorLspTestContext::new_rust(
12400        lsp::ServerCapabilities {
12401            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12402            ..Default::default()
12403        },
12404        cx,
12405    )
12406    .await;
12407
12408    // Set up a buffer white some trailing whitespace and no trailing newline.
12409    cx.set_state(
12410        &[
12411            "one ",   //
12412            "twoˇ",   //
12413            "three ", //
12414            "four",   //
12415        ]
12416        .join("\n"),
12417    );
12418
12419    // Submit a format request.
12420    let format = cx
12421        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12422        .unwrap();
12423
12424    // Record which buffer changes have been sent to the language server
12425    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12426    cx.lsp
12427        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12428            let buffer_changes = buffer_changes.clone();
12429            move |params, _| {
12430                buffer_changes.lock().extend(
12431                    params
12432                        .content_changes
12433                        .into_iter()
12434                        .map(|e| (e.range.unwrap(), e.text)),
12435                );
12436            }
12437        });
12438
12439    // Handle formatting requests to the language server.
12440    cx.lsp
12441        .set_request_handler::<lsp::request::Formatting, _, _>({
12442            let buffer_changes = buffer_changes.clone();
12443            move |_, _| {
12444                // When formatting is requested, trailing whitespace has already been stripped,
12445                // and the trailing newline has already been added.
12446                assert_eq!(
12447                    &buffer_changes.lock()[1..],
12448                    &[
12449                        (
12450                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12451                            "".into()
12452                        ),
12453                        (
12454                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12455                            "".into()
12456                        ),
12457                        (
12458                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12459                            "\n".into()
12460                        ),
12461                    ]
12462                );
12463
12464                // Insert blank lines between each line of the buffer.
12465                async move {
12466                    Ok(Some(vec![
12467                        lsp::TextEdit {
12468                            range: lsp::Range::new(
12469                                lsp::Position::new(1, 0),
12470                                lsp::Position::new(1, 0),
12471                            ),
12472                            new_text: "\n".into(),
12473                        },
12474                        lsp::TextEdit {
12475                            range: lsp::Range::new(
12476                                lsp::Position::new(2, 0),
12477                                lsp::Position::new(2, 0),
12478                            ),
12479                            new_text: "\n".into(),
12480                        },
12481                    ]))
12482                }
12483            }
12484        });
12485
12486    // After formatting the buffer, the trailing whitespace is stripped,
12487    // a newline is appended, and the edits provided by the language server
12488    // have been applied.
12489    format.await.unwrap();
12490    cx.assert_editor_state(
12491        &[
12492            "one",   //
12493            "",      //
12494            "twoˇ",  //
12495            "",      //
12496            "three", //
12497            "four",  //
12498            "",      //
12499        ]
12500        .join("\n"),
12501    );
12502
12503    // Undoing the formatting undoes the trailing whitespace removal, the
12504    // trailing newline, and the LSP edits.
12505    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12506    cx.assert_editor_state(
12507        &[
12508            "one ",   //
12509            "twoˇ",   //
12510            "three ", //
12511            "four",   //
12512        ]
12513        .join("\n"),
12514    );
12515}
12516
12517#[gpui::test]
12518async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12519    cx: &mut TestAppContext,
12520) {
12521    init_test(cx, |_| {});
12522
12523    cx.update(|cx| {
12524        cx.update_global::<SettingsStore, _>(|settings, cx| {
12525            settings.update_user_settings(cx, |settings| {
12526                settings.editor.auto_signature_help = Some(true);
12527            });
12528        });
12529    });
12530
12531    let mut cx = EditorLspTestContext::new_rust(
12532        lsp::ServerCapabilities {
12533            signature_help_provider: Some(lsp::SignatureHelpOptions {
12534                ..Default::default()
12535            }),
12536            ..Default::default()
12537        },
12538        cx,
12539    )
12540    .await;
12541
12542    let language = Language::new(
12543        LanguageConfig {
12544            name: "Rust".into(),
12545            brackets: BracketPairConfig {
12546                pairs: vec![
12547                    BracketPair {
12548                        start: "{".to_string(),
12549                        end: "}".to_string(),
12550                        close: true,
12551                        surround: true,
12552                        newline: true,
12553                    },
12554                    BracketPair {
12555                        start: "(".to_string(),
12556                        end: ")".to_string(),
12557                        close: true,
12558                        surround: true,
12559                        newline: true,
12560                    },
12561                    BracketPair {
12562                        start: "/*".to_string(),
12563                        end: " */".to_string(),
12564                        close: true,
12565                        surround: true,
12566                        newline: true,
12567                    },
12568                    BracketPair {
12569                        start: "[".to_string(),
12570                        end: "]".to_string(),
12571                        close: false,
12572                        surround: false,
12573                        newline: true,
12574                    },
12575                    BracketPair {
12576                        start: "\"".to_string(),
12577                        end: "\"".to_string(),
12578                        close: true,
12579                        surround: true,
12580                        newline: false,
12581                    },
12582                    BracketPair {
12583                        start: "<".to_string(),
12584                        end: ">".to_string(),
12585                        close: false,
12586                        surround: true,
12587                        newline: true,
12588                    },
12589                ],
12590                ..Default::default()
12591            },
12592            autoclose_before: "})]".to_string(),
12593            ..Default::default()
12594        },
12595        Some(tree_sitter_rust::LANGUAGE.into()),
12596    );
12597    let language = Arc::new(language);
12598
12599    cx.language_registry().add(language.clone());
12600    cx.update_buffer(|buffer, cx| {
12601        buffer.set_language(Some(language), cx);
12602    });
12603
12604    cx.set_state(
12605        &r#"
12606            fn main() {
12607                sampleˇ
12608            }
12609        "#
12610        .unindent(),
12611    );
12612
12613    cx.update_editor(|editor, window, cx| {
12614        editor.handle_input("(", window, cx);
12615    });
12616    cx.assert_editor_state(
12617        &"
12618            fn main() {
12619                sample(ˇ)
12620            }
12621        "
12622        .unindent(),
12623    );
12624
12625    let mocked_response = lsp::SignatureHelp {
12626        signatures: vec![lsp::SignatureInformation {
12627            label: "fn sample(param1: u8, param2: u8)".to_string(),
12628            documentation: None,
12629            parameters: Some(vec![
12630                lsp::ParameterInformation {
12631                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12632                    documentation: None,
12633                },
12634                lsp::ParameterInformation {
12635                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12636                    documentation: None,
12637                },
12638            ]),
12639            active_parameter: None,
12640        }],
12641        active_signature: Some(0),
12642        active_parameter: Some(0),
12643    };
12644    handle_signature_help_request(&mut cx, mocked_response).await;
12645
12646    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12647        .await;
12648
12649    cx.editor(|editor, _, _| {
12650        let signature_help_state = editor.signature_help_state.popover().cloned();
12651        let signature = signature_help_state.unwrap();
12652        assert_eq!(
12653            signature.signatures[signature.current_signature].label,
12654            "fn sample(param1: u8, param2: u8)"
12655        );
12656    });
12657}
12658
12659#[gpui::test]
12660async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
12661    init_test(cx, |_| {});
12662
12663    cx.update(|cx| {
12664        cx.update_global::<SettingsStore, _>(|settings, cx| {
12665            settings.update_user_settings(cx, |settings| {
12666                settings.editor.auto_signature_help = Some(false);
12667                settings.editor.show_signature_help_after_edits = Some(false);
12668            });
12669        });
12670    });
12671
12672    let mut cx = EditorLspTestContext::new_rust(
12673        lsp::ServerCapabilities {
12674            signature_help_provider: Some(lsp::SignatureHelpOptions {
12675                ..Default::default()
12676            }),
12677            ..Default::default()
12678        },
12679        cx,
12680    )
12681    .await;
12682
12683    let language = Language::new(
12684        LanguageConfig {
12685            name: "Rust".into(),
12686            brackets: BracketPairConfig {
12687                pairs: vec![
12688                    BracketPair {
12689                        start: "{".to_string(),
12690                        end: "}".to_string(),
12691                        close: true,
12692                        surround: true,
12693                        newline: true,
12694                    },
12695                    BracketPair {
12696                        start: "(".to_string(),
12697                        end: ")".to_string(),
12698                        close: true,
12699                        surround: true,
12700                        newline: true,
12701                    },
12702                    BracketPair {
12703                        start: "/*".to_string(),
12704                        end: " */".to_string(),
12705                        close: true,
12706                        surround: true,
12707                        newline: true,
12708                    },
12709                    BracketPair {
12710                        start: "[".to_string(),
12711                        end: "]".to_string(),
12712                        close: false,
12713                        surround: false,
12714                        newline: true,
12715                    },
12716                    BracketPair {
12717                        start: "\"".to_string(),
12718                        end: "\"".to_string(),
12719                        close: true,
12720                        surround: true,
12721                        newline: false,
12722                    },
12723                    BracketPair {
12724                        start: "<".to_string(),
12725                        end: ">".to_string(),
12726                        close: false,
12727                        surround: true,
12728                        newline: true,
12729                    },
12730                ],
12731                ..Default::default()
12732            },
12733            autoclose_before: "})]".to_string(),
12734            ..Default::default()
12735        },
12736        Some(tree_sitter_rust::LANGUAGE.into()),
12737    );
12738    let language = Arc::new(language);
12739
12740    cx.language_registry().add(language.clone());
12741    cx.update_buffer(|buffer, cx| {
12742        buffer.set_language(Some(language), cx);
12743    });
12744
12745    // Ensure that signature_help is not called when no signature help is enabled.
12746    cx.set_state(
12747        &r#"
12748            fn main() {
12749                sampleˇ
12750            }
12751        "#
12752        .unindent(),
12753    );
12754    cx.update_editor(|editor, window, cx| {
12755        editor.handle_input("(", window, cx);
12756    });
12757    cx.assert_editor_state(
12758        &"
12759            fn main() {
12760                sample(ˇ)
12761            }
12762        "
12763        .unindent(),
12764    );
12765    cx.editor(|editor, _, _| {
12766        assert!(editor.signature_help_state.task().is_none());
12767    });
12768
12769    let mocked_response = lsp::SignatureHelp {
12770        signatures: vec![lsp::SignatureInformation {
12771            label: "fn sample(param1: u8, param2: u8)".to_string(),
12772            documentation: None,
12773            parameters: Some(vec![
12774                lsp::ParameterInformation {
12775                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12776                    documentation: None,
12777                },
12778                lsp::ParameterInformation {
12779                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12780                    documentation: None,
12781                },
12782            ]),
12783            active_parameter: None,
12784        }],
12785        active_signature: Some(0),
12786        active_parameter: Some(0),
12787    };
12788
12789    // Ensure that signature_help is called when enabled afte edits
12790    cx.update(|_, cx| {
12791        cx.update_global::<SettingsStore, _>(|settings, cx| {
12792            settings.update_user_settings(cx, |settings| {
12793                settings.editor.auto_signature_help = Some(false);
12794                settings.editor.show_signature_help_after_edits = Some(true);
12795            });
12796        });
12797    });
12798    cx.set_state(
12799        &r#"
12800            fn main() {
12801                sampleˇ
12802            }
12803        "#
12804        .unindent(),
12805    );
12806    cx.update_editor(|editor, window, cx| {
12807        editor.handle_input("(", window, cx);
12808    });
12809    cx.assert_editor_state(
12810        &"
12811            fn main() {
12812                sample(ˇ)
12813            }
12814        "
12815        .unindent(),
12816    );
12817    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12818    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12819        .await;
12820    cx.update_editor(|editor, _, _| {
12821        let signature_help_state = editor.signature_help_state.popover().cloned();
12822        assert!(signature_help_state.is_some());
12823        let signature = signature_help_state.unwrap();
12824        assert_eq!(
12825            signature.signatures[signature.current_signature].label,
12826            "fn sample(param1: u8, param2: u8)"
12827        );
12828        editor.signature_help_state = SignatureHelpState::default();
12829    });
12830
12831    // Ensure that signature_help is called when auto signature help override is enabled
12832    cx.update(|_, cx| {
12833        cx.update_global::<SettingsStore, _>(|settings, cx| {
12834            settings.update_user_settings(cx, |settings| {
12835                settings.editor.auto_signature_help = Some(true);
12836                settings.editor.show_signature_help_after_edits = Some(false);
12837            });
12838        });
12839    });
12840    cx.set_state(
12841        &r#"
12842            fn main() {
12843                sampleˇ
12844            }
12845        "#
12846        .unindent(),
12847    );
12848    cx.update_editor(|editor, window, cx| {
12849        editor.handle_input("(", window, cx);
12850    });
12851    cx.assert_editor_state(
12852        &"
12853            fn main() {
12854                sample(ˇ)
12855            }
12856        "
12857        .unindent(),
12858    );
12859    handle_signature_help_request(&mut cx, mocked_response).await;
12860    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12861        .await;
12862    cx.editor(|editor, _, _| {
12863        let signature_help_state = editor.signature_help_state.popover().cloned();
12864        assert!(signature_help_state.is_some());
12865        let signature = signature_help_state.unwrap();
12866        assert_eq!(
12867            signature.signatures[signature.current_signature].label,
12868            "fn sample(param1: u8, param2: u8)"
12869        );
12870    });
12871}
12872
12873#[gpui::test]
12874async fn test_signature_help(cx: &mut TestAppContext) {
12875    init_test(cx, |_| {});
12876    cx.update(|cx| {
12877        cx.update_global::<SettingsStore, _>(|settings, cx| {
12878            settings.update_user_settings(cx, |settings| {
12879                settings.editor.auto_signature_help = Some(true);
12880            });
12881        });
12882    });
12883
12884    let mut cx = EditorLspTestContext::new_rust(
12885        lsp::ServerCapabilities {
12886            signature_help_provider: Some(lsp::SignatureHelpOptions {
12887                ..Default::default()
12888            }),
12889            ..Default::default()
12890        },
12891        cx,
12892    )
12893    .await;
12894
12895    // A test that directly calls `show_signature_help`
12896    cx.update_editor(|editor, window, cx| {
12897        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12898    });
12899
12900    let mocked_response = lsp::SignatureHelp {
12901        signatures: vec![lsp::SignatureInformation {
12902            label: "fn sample(param1: u8, param2: u8)".to_string(),
12903            documentation: None,
12904            parameters: Some(vec![
12905                lsp::ParameterInformation {
12906                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12907                    documentation: None,
12908                },
12909                lsp::ParameterInformation {
12910                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12911                    documentation: None,
12912                },
12913            ]),
12914            active_parameter: None,
12915        }],
12916        active_signature: Some(0),
12917        active_parameter: Some(0),
12918    };
12919    handle_signature_help_request(&mut cx, mocked_response).await;
12920
12921    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12922        .await;
12923
12924    cx.editor(|editor, _, _| {
12925        let signature_help_state = editor.signature_help_state.popover().cloned();
12926        assert!(signature_help_state.is_some());
12927        let signature = signature_help_state.unwrap();
12928        assert_eq!(
12929            signature.signatures[signature.current_signature].label,
12930            "fn sample(param1: u8, param2: u8)"
12931        );
12932    });
12933
12934    // When exiting outside from inside the brackets, `signature_help` is closed.
12935    cx.set_state(indoc! {"
12936        fn main() {
12937            sample(ˇ);
12938        }
12939
12940        fn sample(param1: u8, param2: u8) {}
12941    "});
12942
12943    cx.update_editor(|editor, window, cx| {
12944        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12945            s.select_ranges([0..0])
12946        });
12947    });
12948
12949    let mocked_response = lsp::SignatureHelp {
12950        signatures: Vec::new(),
12951        active_signature: None,
12952        active_parameter: None,
12953    };
12954    handle_signature_help_request(&mut cx, mocked_response).await;
12955
12956    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
12957        .await;
12958
12959    cx.editor(|editor, _, _| {
12960        assert!(!editor.signature_help_state.is_shown());
12961    });
12962
12963    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
12964    cx.set_state(indoc! {"
12965        fn main() {
12966            sample(ˇ);
12967        }
12968
12969        fn sample(param1: u8, param2: u8) {}
12970    "});
12971
12972    let mocked_response = lsp::SignatureHelp {
12973        signatures: vec![lsp::SignatureInformation {
12974            label: "fn sample(param1: u8, param2: u8)".to_string(),
12975            documentation: None,
12976            parameters: Some(vec![
12977                lsp::ParameterInformation {
12978                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12979                    documentation: None,
12980                },
12981                lsp::ParameterInformation {
12982                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12983                    documentation: None,
12984                },
12985            ]),
12986            active_parameter: None,
12987        }],
12988        active_signature: Some(0),
12989        active_parameter: Some(0),
12990    };
12991    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12992    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12993        .await;
12994    cx.editor(|editor, _, _| {
12995        assert!(editor.signature_help_state.is_shown());
12996    });
12997
12998    // Restore the popover with more parameter input
12999    cx.set_state(indoc! {"
13000        fn main() {
13001            sample(param1, param2ˇ);
13002        }
13003
13004        fn sample(param1: u8, param2: u8) {}
13005    "});
13006
13007    let mocked_response = lsp::SignatureHelp {
13008        signatures: vec![lsp::SignatureInformation {
13009            label: "fn sample(param1: u8, param2: u8)".to_string(),
13010            documentation: None,
13011            parameters: Some(vec![
13012                lsp::ParameterInformation {
13013                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13014                    documentation: None,
13015                },
13016                lsp::ParameterInformation {
13017                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13018                    documentation: None,
13019                },
13020            ]),
13021            active_parameter: None,
13022        }],
13023        active_signature: Some(0),
13024        active_parameter: Some(1),
13025    };
13026    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13027    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13028        .await;
13029
13030    // When selecting a range, the popover is gone.
13031    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
13032    cx.update_editor(|editor, window, cx| {
13033        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13034            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13035        })
13036    });
13037    cx.assert_editor_state(indoc! {"
13038        fn main() {
13039            sample(param1, «ˇparam2»);
13040        }
13041
13042        fn sample(param1: u8, param2: u8) {}
13043    "});
13044    cx.editor(|editor, _, _| {
13045        assert!(!editor.signature_help_state.is_shown());
13046    });
13047
13048    // When unselecting again, the popover is back if within the brackets.
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, 19)..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    handle_signature_help_request(&mut cx, mocked_response).await;
13062    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13063        .await;
13064    cx.editor(|editor, _, _| {
13065        assert!(editor.signature_help_state.is_shown());
13066    });
13067
13068    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13069    cx.update_editor(|editor, window, cx| {
13070        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13071            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13072            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13073        })
13074    });
13075    cx.assert_editor_state(indoc! {"
13076        fn main() {
13077            sample(param1, ˇparam2);
13078        }
13079
13080        fn sample(param1: u8, param2: u8) {}
13081    "});
13082
13083    let mocked_response = lsp::SignatureHelp {
13084        signatures: vec![lsp::SignatureInformation {
13085            label: "fn sample(param1: u8, param2: u8)".to_string(),
13086            documentation: None,
13087            parameters: Some(vec![
13088                lsp::ParameterInformation {
13089                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13090                    documentation: None,
13091                },
13092                lsp::ParameterInformation {
13093                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13094                    documentation: None,
13095                },
13096            ]),
13097            active_parameter: None,
13098        }],
13099        active_signature: Some(0),
13100        active_parameter: Some(1),
13101    };
13102    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13103    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13104        .await;
13105    cx.update_editor(|editor, _, cx| {
13106        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13107    });
13108    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13109        .await;
13110    cx.update_editor(|editor, window, cx| {
13111        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13112            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13113        })
13114    });
13115    cx.assert_editor_state(indoc! {"
13116        fn main() {
13117            sample(param1, «ˇparam2»);
13118        }
13119
13120        fn sample(param1: u8, param2: u8) {}
13121    "});
13122    cx.update_editor(|editor, window, cx| {
13123        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13124            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13125        })
13126    });
13127    cx.assert_editor_state(indoc! {"
13128        fn main() {
13129            sample(param1, ˇparam2);
13130        }
13131
13132        fn sample(param1: u8, param2: u8) {}
13133    "});
13134    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13135        .await;
13136}
13137
13138#[gpui::test]
13139async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13140    init_test(cx, |_| {});
13141
13142    let mut cx = EditorLspTestContext::new_rust(
13143        lsp::ServerCapabilities {
13144            signature_help_provider: Some(lsp::SignatureHelpOptions {
13145                ..Default::default()
13146            }),
13147            ..Default::default()
13148        },
13149        cx,
13150    )
13151    .await;
13152
13153    cx.set_state(indoc! {"
13154        fn main() {
13155            overloadedˇ
13156        }
13157    "});
13158
13159    cx.update_editor(|editor, window, cx| {
13160        editor.handle_input("(", window, cx);
13161        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13162    });
13163
13164    // Mock response with 3 signatures
13165    let mocked_response = lsp::SignatureHelp {
13166        signatures: vec![
13167            lsp::SignatureInformation {
13168                label: "fn overloaded(x: i32)".to_string(),
13169                documentation: None,
13170                parameters: Some(vec![lsp::ParameterInformation {
13171                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13172                    documentation: None,
13173                }]),
13174                active_parameter: None,
13175            },
13176            lsp::SignatureInformation {
13177                label: "fn overloaded(x: i32, y: i32)".to_string(),
13178                documentation: None,
13179                parameters: Some(vec![
13180                    lsp::ParameterInformation {
13181                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13182                        documentation: None,
13183                    },
13184                    lsp::ParameterInformation {
13185                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13186                        documentation: None,
13187                    },
13188                ]),
13189                active_parameter: None,
13190            },
13191            lsp::SignatureInformation {
13192                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13193                documentation: None,
13194                parameters: Some(vec![
13195                    lsp::ParameterInformation {
13196                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13197                        documentation: None,
13198                    },
13199                    lsp::ParameterInformation {
13200                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13201                        documentation: None,
13202                    },
13203                    lsp::ParameterInformation {
13204                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13205                        documentation: None,
13206                    },
13207                ]),
13208                active_parameter: None,
13209            },
13210        ],
13211        active_signature: Some(1),
13212        active_parameter: Some(0),
13213    };
13214    handle_signature_help_request(&mut cx, mocked_response).await;
13215
13216    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13217        .await;
13218
13219    // Verify we have multiple signatures and the right one is selected
13220    cx.editor(|editor, _, _| {
13221        let popover = editor.signature_help_state.popover().cloned().unwrap();
13222        assert_eq!(popover.signatures.len(), 3);
13223        // active_signature was 1, so that should be the current
13224        assert_eq!(popover.current_signature, 1);
13225        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13226        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13227        assert_eq!(
13228            popover.signatures[2].label,
13229            "fn overloaded(x: i32, y: i32, z: i32)"
13230        );
13231    });
13232
13233    // Test navigation functionality
13234    cx.update_editor(|editor, window, cx| {
13235        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13236    });
13237
13238    cx.editor(|editor, _, _| {
13239        let popover = editor.signature_help_state.popover().cloned().unwrap();
13240        assert_eq!(popover.current_signature, 2);
13241    });
13242
13243    // Test wrap around
13244    cx.update_editor(|editor, window, cx| {
13245        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13246    });
13247
13248    cx.editor(|editor, _, _| {
13249        let popover = editor.signature_help_state.popover().cloned().unwrap();
13250        assert_eq!(popover.current_signature, 0);
13251    });
13252
13253    // Test previous navigation
13254    cx.update_editor(|editor, window, cx| {
13255        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13256    });
13257
13258    cx.editor(|editor, _, _| {
13259        let popover = editor.signature_help_state.popover().cloned().unwrap();
13260        assert_eq!(popover.current_signature, 2);
13261    });
13262}
13263
13264#[gpui::test]
13265async fn test_completion_mode(cx: &mut TestAppContext) {
13266    init_test(cx, |_| {});
13267    let mut cx = EditorLspTestContext::new_rust(
13268        lsp::ServerCapabilities {
13269            completion_provider: Some(lsp::CompletionOptions {
13270                resolve_provider: Some(true),
13271                ..Default::default()
13272            }),
13273            ..Default::default()
13274        },
13275        cx,
13276    )
13277    .await;
13278
13279    struct Run {
13280        run_description: &'static str,
13281        initial_state: String,
13282        buffer_marked_text: String,
13283        completion_label: &'static str,
13284        completion_text: &'static str,
13285        expected_with_insert_mode: String,
13286        expected_with_replace_mode: String,
13287        expected_with_replace_subsequence_mode: String,
13288        expected_with_replace_suffix_mode: String,
13289    }
13290
13291    let runs = [
13292        Run {
13293            run_description: "Start of word matches completion text",
13294            initial_state: "before ediˇ after".into(),
13295            buffer_marked_text: "before <edi|> after".into(),
13296            completion_label: "editor",
13297            completion_text: "editor",
13298            expected_with_insert_mode: "before editorˇ after".into(),
13299            expected_with_replace_mode: "before editorˇ after".into(),
13300            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13301            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13302        },
13303        Run {
13304            run_description: "Accept same text at the middle of the word",
13305            initial_state: "before ediˇtor after".into(),
13306            buffer_marked_text: "before <edi|tor> after".into(),
13307            completion_label: "editor",
13308            completion_text: "editor",
13309            expected_with_insert_mode: "before editorˇtor after".into(),
13310            expected_with_replace_mode: "before editorˇ after".into(),
13311            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13312            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13313        },
13314        Run {
13315            run_description: "End of word matches completion text -- cursor at end",
13316            initial_state: "before torˇ after".into(),
13317            buffer_marked_text: "before <tor|> after".into(),
13318            completion_label: "editor",
13319            completion_text: "editor",
13320            expected_with_insert_mode: "before editorˇ after".into(),
13321            expected_with_replace_mode: "before editorˇ after".into(),
13322            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13323            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13324        },
13325        Run {
13326            run_description: "End of word matches completion text -- cursor at start",
13327            initial_state: "before ˇtor after".into(),
13328            buffer_marked_text: "before <|tor> after".into(),
13329            completion_label: "editor",
13330            completion_text: "editor",
13331            expected_with_insert_mode: "before editorˇtor after".into(),
13332            expected_with_replace_mode: "before editorˇ after".into(),
13333            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13334            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13335        },
13336        Run {
13337            run_description: "Prepend text containing whitespace",
13338            initial_state: "pˇfield: bool".into(),
13339            buffer_marked_text: "<p|field>: bool".into(),
13340            completion_label: "pub ",
13341            completion_text: "pub ",
13342            expected_with_insert_mode: "pub ˇfield: bool".into(),
13343            expected_with_replace_mode: "pub ˇ: bool".into(),
13344            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13345            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13346        },
13347        Run {
13348            run_description: "Add element to start of list",
13349            initial_state: "[element_ˇelement_2]".into(),
13350            buffer_marked_text: "[<element_|element_2>]".into(),
13351            completion_label: "element_1",
13352            completion_text: "element_1",
13353            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13354            expected_with_replace_mode: "[element_1ˇ]".into(),
13355            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13356            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13357        },
13358        Run {
13359            run_description: "Add element to start of list -- first and second elements are equal",
13360            initial_state: "[elˇelement]".into(),
13361            buffer_marked_text: "[<el|element>]".into(),
13362            completion_label: "element",
13363            completion_text: "element",
13364            expected_with_insert_mode: "[elementˇelement]".into(),
13365            expected_with_replace_mode: "[elementˇ]".into(),
13366            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13367            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13368        },
13369        Run {
13370            run_description: "Ends with matching suffix",
13371            initial_state: "SubˇError".into(),
13372            buffer_marked_text: "<Sub|Error>".into(),
13373            completion_label: "SubscriptionError",
13374            completion_text: "SubscriptionError",
13375            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13376            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13377            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13378            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13379        },
13380        Run {
13381            run_description: "Suffix is a subsequence -- contiguous",
13382            initial_state: "SubˇErr".into(),
13383            buffer_marked_text: "<Sub|Err>".into(),
13384            completion_label: "SubscriptionError",
13385            completion_text: "SubscriptionError",
13386            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13387            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13388            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13389            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13390        },
13391        Run {
13392            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13393            initial_state: "Suˇscrirr".into(),
13394            buffer_marked_text: "<Su|scrirr>".into(),
13395            completion_label: "SubscriptionError",
13396            completion_text: "SubscriptionError",
13397            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13398            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13399            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13400            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13401        },
13402        Run {
13403            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13404            initial_state: "foo(indˇix)".into(),
13405            buffer_marked_text: "foo(<ind|ix>)".into(),
13406            completion_label: "node_index",
13407            completion_text: "node_index",
13408            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13409            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13410            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13411            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13412        },
13413        Run {
13414            run_description: "Replace range ends before cursor - should extend to cursor",
13415            initial_state: "before editˇo after".into(),
13416            buffer_marked_text: "before <{ed}>it|o after".into(),
13417            completion_label: "editor",
13418            completion_text: "editor",
13419            expected_with_insert_mode: "before editorˇo after".into(),
13420            expected_with_replace_mode: "before editorˇo after".into(),
13421            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13422            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13423        },
13424        Run {
13425            run_description: "Uses label for suffix matching",
13426            initial_state: "before ediˇtor after".into(),
13427            buffer_marked_text: "before <edi|tor> after".into(),
13428            completion_label: "editor",
13429            completion_text: "editor()",
13430            expected_with_insert_mode: "before editor()ˇtor after".into(),
13431            expected_with_replace_mode: "before editor()ˇ after".into(),
13432            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13433            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13434        },
13435        Run {
13436            run_description: "Case insensitive subsequence and suffix matching",
13437            initial_state: "before EDiˇtoR after".into(),
13438            buffer_marked_text: "before <EDi|toR> after".into(),
13439            completion_label: "editor",
13440            completion_text: "editor",
13441            expected_with_insert_mode: "before editorˇtoR after".into(),
13442            expected_with_replace_mode: "before editorˇ after".into(),
13443            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13444            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13445        },
13446    ];
13447
13448    for run in runs {
13449        let run_variations = [
13450            (LspInsertMode::Insert, run.expected_with_insert_mode),
13451            (LspInsertMode::Replace, run.expected_with_replace_mode),
13452            (
13453                LspInsertMode::ReplaceSubsequence,
13454                run.expected_with_replace_subsequence_mode,
13455            ),
13456            (
13457                LspInsertMode::ReplaceSuffix,
13458                run.expected_with_replace_suffix_mode,
13459            ),
13460        ];
13461
13462        for (lsp_insert_mode, expected_text) in run_variations {
13463            eprintln!(
13464                "run = {:?}, mode = {lsp_insert_mode:.?}",
13465                run.run_description,
13466            );
13467
13468            update_test_language_settings(&mut cx, |settings| {
13469                settings.defaults.completions = Some(CompletionSettingsContent {
13470                    lsp_insert_mode: Some(lsp_insert_mode),
13471                    words: Some(WordsCompletionMode::Disabled),
13472                    words_min_length: Some(0),
13473                    ..Default::default()
13474                });
13475            });
13476
13477            cx.set_state(&run.initial_state);
13478            cx.update_editor(|editor, window, cx| {
13479                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13480            });
13481
13482            let counter = Arc::new(AtomicUsize::new(0));
13483            handle_completion_request_with_insert_and_replace(
13484                &mut cx,
13485                &run.buffer_marked_text,
13486                vec![(run.completion_label, run.completion_text)],
13487                counter.clone(),
13488            )
13489            .await;
13490            cx.condition(|editor, _| editor.context_menu_visible())
13491                .await;
13492            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13493
13494            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13495                editor
13496                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13497                    .unwrap()
13498            });
13499            cx.assert_editor_state(&expected_text);
13500            handle_resolve_completion_request(&mut cx, None).await;
13501            apply_additional_edits.await.unwrap();
13502        }
13503    }
13504}
13505
13506#[gpui::test]
13507async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13508    init_test(cx, |_| {});
13509    let mut cx = EditorLspTestContext::new_rust(
13510        lsp::ServerCapabilities {
13511            completion_provider: Some(lsp::CompletionOptions {
13512                resolve_provider: Some(true),
13513                ..Default::default()
13514            }),
13515            ..Default::default()
13516        },
13517        cx,
13518    )
13519    .await;
13520
13521    let initial_state = "SubˇError";
13522    let buffer_marked_text = "<Sub|Error>";
13523    let completion_text = "SubscriptionError";
13524    let expected_with_insert_mode = "SubscriptionErrorˇError";
13525    let expected_with_replace_mode = "SubscriptionErrorˇ";
13526
13527    update_test_language_settings(&mut cx, |settings| {
13528        settings.defaults.completions = Some(CompletionSettingsContent {
13529            words: Some(WordsCompletionMode::Disabled),
13530            words_min_length: Some(0),
13531            // set the opposite here to ensure that the action is overriding the default behavior
13532            lsp_insert_mode: Some(LspInsertMode::Insert),
13533            ..Default::default()
13534        });
13535    });
13536
13537    cx.set_state(initial_state);
13538    cx.update_editor(|editor, window, cx| {
13539        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13540    });
13541
13542    let counter = Arc::new(AtomicUsize::new(0));
13543    handle_completion_request_with_insert_and_replace(
13544        &mut cx,
13545        buffer_marked_text,
13546        vec![(completion_text, completion_text)],
13547        counter.clone(),
13548    )
13549    .await;
13550    cx.condition(|editor, _| editor.context_menu_visible())
13551        .await;
13552    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13553
13554    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13555        editor
13556            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13557            .unwrap()
13558    });
13559    cx.assert_editor_state(expected_with_replace_mode);
13560    handle_resolve_completion_request(&mut cx, None).await;
13561    apply_additional_edits.await.unwrap();
13562
13563    update_test_language_settings(&mut cx, |settings| {
13564        settings.defaults.completions = Some(CompletionSettingsContent {
13565            words: Some(WordsCompletionMode::Disabled),
13566            words_min_length: Some(0),
13567            // set the opposite here to ensure that the action is overriding the default behavior
13568            lsp_insert_mode: Some(LspInsertMode::Replace),
13569            ..Default::default()
13570        });
13571    });
13572
13573    cx.set_state(initial_state);
13574    cx.update_editor(|editor, window, cx| {
13575        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13576    });
13577    handle_completion_request_with_insert_and_replace(
13578        &mut cx,
13579        buffer_marked_text,
13580        vec![(completion_text, completion_text)],
13581        counter.clone(),
13582    )
13583    .await;
13584    cx.condition(|editor, _| editor.context_menu_visible())
13585        .await;
13586    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13587
13588    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13589        editor
13590            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13591            .unwrap()
13592    });
13593    cx.assert_editor_state(expected_with_insert_mode);
13594    handle_resolve_completion_request(&mut cx, None).await;
13595    apply_additional_edits.await.unwrap();
13596}
13597
13598#[gpui::test]
13599async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
13600    init_test(cx, |_| {});
13601    let mut cx = EditorLspTestContext::new_rust(
13602        lsp::ServerCapabilities {
13603            completion_provider: Some(lsp::CompletionOptions {
13604                resolve_provider: Some(true),
13605                ..Default::default()
13606            }),
13607            ..Default::default()
13608        },
13609        cx,
13610    )
13611    .await;
13612
13613    // scenario: surrounding text matches completion text
13614    let completion_text = "to_offset";
13615    let initial_state = indoc! {"
13616        1. buf.to_offˇsuffix
13617        2. buf.to_offˇsuf
13618        3. buf.to_offˇfix
13619        4. buf.to_offˇ
13620        5. into_offˇensive
13621        6. ˇsuffix
13622        7. let ˇ //
13623        8. aaˇzz
13624        9. buf.to_off«zzzzzˇ»suffix
13625        10. buf.«ˇzzzzz»suffix
13626        11. to_off«ˇzzzzz»
13627
13628        buf.to_offˇsuffix  // newest cursor
13629    "};
13630    let completion_marked_buffer = indoc! {"
13631        1. buf.to_offsuffix
13632        2. buf.to_offsuf
13633        3. buf.to_offfix
13634        4. buf.to_off
13635        5. into_offensive
13636        6. suffix
13637        7. let  //
13638        8. aazz
13639        9. buf.to_offzzzzzsuffix
13640        10. buf.zzzzzsuffix
13641        11. to_offzzzzz
13642
13643        buf.<to_off|suffix>  // newest cursor
13644    "};
13645    let expected = indoc! {"
13646        1. buf.to_offsetˇ
13647        2. buf.to_offsetˇsuf
13648        3. buf.to_offsetˇfix
13649        4. buf.to_offsetˇ
13650        5. into_offsetˇensive
13651        6. to_offsetˇsuffix
13652        7. let to_offsetˇ //
13653        8. aato_offsetˇzz
13654        9. buf.to_offsetˇ
13655        10. buf.to_offsetˇsuffix
13656        11. to_offsetˇ
13657
13658        buf.to_offsetˇ  // newest cursor
13659    "};
13660    cx.set_state(initial_state);
13661    cx.update_editor(|editor, window, cx| {
13662        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13663    });
13664    handle_completion_request_with_insert_and_replace(
13665        &mut cx,
13666        completion_marked_buffer,
13667        vec![(completion_text, completion_text)],
13668        Arc::new(AtomicUsize::new(0)),
13669    )
13670    .await;
13671    cx.condition(|editor, _| editor.context_menu_visible())
13672        .await;
13673    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13674        editor
13675            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13676            .unwrap()
13677    });
13678    cx.assert_editor_state(expected);
13679    handle_resolve_completion_request(&mut cx, None).await;
13680    apply_additional_edits.await.unwrap();
13681
13682    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
13683    let completion_text = "foo_and_bar";
13684    let initial_state = indoc! {"
13685        1. ooanbˇ
13686        2. zooanbˇ
13687        3. ooanbˇz
13688        4. zooanbˇz
13689        5. ooanˇ
13690        6. oanbˇ
13691
13692        ooanbˇ
13693    "};
13694    let completion_marked_buffer = indoc! {"
13695        1. ooanb
13696        2. zooanb
13697        3. ooanbz
13698        4. zooanbz
13699        5. ooan
13700        6. oanb
13701
13702        <ooanb|>
13703    "};
13704    let expected = indoc! {"
13705        1. foo_and_barˇ
13706        2. zfoo_and_barˇ
13707        3. foo_and_barˇz
13708        4. zfoo_and_barˇz
13709        5. ooanfoo_and_barˇ
13710        6. oanbfoo_and_barˇ
13711
13712        foo_and_barˇ
13713    "};
13714    cx.set_state(initial_state);
13715    cx.update_editor(|editor, window, cx| {
13716        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13717    });
13718    handle_completion_request_with_insert_and_replace(
13719        &mut cx,
13720        completion_marked_buffer,
13721        vec![(completion_text, completion_text)],
13722        Arc::new(AtomicUsize::new(0)),
13723    )
13724    .await;
13725    cx.condition(|editor, _| editor.context_menu_visible())
13726        .await;
13727    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13728        editor
13729            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13730            .unwrap()
13731    });
13732    cx.assert_editor_state(expected);
13733    handle_resolve_completion_request(&mut cx, None).await;
13734    apply_additional_edits.await.unwrap();
13735
13736    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
13737    // (expects the same as if it was inserted at the end)
13738    let completion_text = "foo_and_bar";
13739    let initial_state = indoc! {"
13740        1. ooˇanb
13741        2. zooˇanb
13742        3. ooˇanbz
13743        4. zooˇanbz
13744
13745        ooˇanb
13746    "};
13747    let completion_marked_buffer = indoc! {"
13748        1. ooanb
13749        2. zooanb
13750        3. ooanbz
13751        4. zooanbz
13752
13753        <oo|anb>
13754    "};
13755    let expected = indoc! {"
13756        1. foo_and_barˇ
13757        2. zfoo_and_barˇ
13758        3. foo_and_barˇz
13759        4. zfoo_and_barˇz
13760
13761        foo_and_barˇ
13762    "};
13763    cx.set_state(initial_state);
13764    cx.update_editor(|editor, window, cx| {
13765        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13766    });
13767    handle_completion_request_with_insert_and_replace(
13768        &mut cx,
13769        completion_marked_buffer,
13770        vec![(completion_text, completion_text)],
13771        Arc::new(AtomicUsize::new(0)),
13772    )
13773    .await;
13774    cx.condition(|editor, _| editor.context_menu_visible())
13775        .await;
13776    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13777        editor
13778            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13779            .unwrap()
13780    });
13781    cx.assert_editor_state(expected);
13782    handle_resolve_completion_request(&mut cx, None).await;
13783    apply_additional_edits.await.unwrap();
13784}
13785
13786// This used to crash
13787#[gpui::test]
13788async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
13789    init_test(cx, |_| {});
13790
13791    let buffer_text = indoc! {"
13792        fn main() {
13793            10.satu;
13794
13795            //
13796            // separate cursors so they open in different excerpts (manually reproducible)
13797            //
13798
13799            10.satu20;
13800        }
13801    "};
13802    let multibuffer_text_with_selections = indoc! {"
13803        fn main() {
13804            10.satuˇ;
13805
13806            //
13807
13808            //
13809
13810            10.satuˇ20;
13811        }
13812    "};
13813    let expected_multibuffer = indoc! {"
13814        fn main() {
13815            10.saturating_sub()ˇ;
13816
13817            //
13818
13819            //
13820
13821            10.saturating_sub()ˇ;
13822        }
13823    "};
13824
13825    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
13826    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
13827
13828    let fs = FakeFs::new(cx.executor());
13829    fs.insert_tree(
13830        path!("/a"),
13831        json!({
13832            "main.rs": buffer_text,
13833        }),
13834    )
13835    .await;
13836
13837    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13838    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13839    language_registry.add(rust_lang());
13840    let mut fake_servers = language_registry.register_fake_lsp(
13841        "Rust",
13842        FakeLspAdapter {
13843            capabilities: lsp::ServerCapabilities {
13844                completion_provider: Some(lsp::CompletionOptions {
13845                    resolve_provider: None,
13846                    ..lsp::CompletionOptions::default()
13847                }),
13848                ..lsp::ServerCapabilities::default()
13849            },
13850            ..FakeLspAdapter::default()
13851        },
13852    );
13853    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13854    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13855    let buffer = project
13856        .update(cx, |project, cx| {
13857            project.open_local_buffer(path!("/a/main.rs"), cx)
13858        })
13859        .await
13860        .unwrap();
13861
13862    let multi_buffer = cx.new(|cx| {
13863        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
13864        multi_buffer.push_excerpts(
13865            buffer.clone(),
13866            [ExcerptRange::new(0..first_excerpt_end)],
13867            cx,
13868        );
13869        multi_buffer.push_excerpts(
13870            buffer.clone(),
13871            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
13872            cx,
13873        );
13874        multi_buffer
13875    });
13876
13877    let editor = workspace
13878        .update(cx, |_, window, cx| {
13879            cx.new(|cx| {
13880                Editor::new(
13881                    EditorMode::Full {
13882                        scale_ui_elements_with_buffer_font_size: false,
13883                        show_active_line_background: false,
13884                        sized_by_content: false,
13885                    },
13886                    multi_buffer.clone(),
13887                    Some(project.clone()),
13888                    window,
13889                    cx,
13890                )
13891            })
13892        })
13893        .unwrap();
13894
13895    let pane = workspace
13896        .update(cx, |workspace, _, _| workspace.active_pane().clone())
13897        .unwrap();
13898    pane.update_in(cx, |pane, window, cx| {
13899        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
13900    });
13901
13902    let fake_server = fake_servers.next().await.unwrap();
13903
13904    editor.update_in(cx, |editor, window, cx| {
13905        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13906            s.select_ranges([
13907                Point::new(1, 11)..Point::new(1, 11),
13908                Point::new(7, 11)..Point::new(7, 11),
13909            ])
13910        });
13911
13912        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
13913    });
13914
13915    editor.update_in(cx, |editor, window, cx| {
13916        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13917    });
13918
13919    fake_server
13920        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13921            let completion_item = lsp::CompletionItem {
13922                label: "saturating_sub()".into(),
13923                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
13924                    lsp::InsertReplaceEdit {
13925                        new_text: "saturating_sub()".to_owned(),
13926                        insert: lsp::Range::new(
13927                            lsp::Position::new(7, 7),
13928                            lsp::Position::new(7, 11),
13929                        ),
13930                        replace: lsp::Range::new(
13931                            lsp::Position::new(7, 7),
13932                            lsp::Position::new(7, 13),
13933                        ),
13934                    },
13935                )),
13936                ..lsp::CompletionItem::default()
13937            };
13938
13939            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
13940        })
13941        .next()
13942        .await
13943        .unwrap();
13944
13945    cx.condition(&editor, |editor, _| editor.context_menu_visible())
13946        .await;
13947
13948    editor
13949        .update_in(cx, |editor, window, cx| {
13950            editor
13951                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13952                .unwrap()
13953        })
13954        .await
13955        .unwrap();
13956
13957    editor.update(cx, |editor, cx| {
13958        assert_text_with_selections(editor, expected_multibuffer, cx);
13959    })
13960}
13961
13962#[gpui::test]
13963async fn test_completion(cx: &mut TestAppContext) {
13964    init_test(cx, |_| {});
13965
13966    let mut cx = EditorLspTestContext::new_rust(
13967        lsp::ServerCapabilities {
13968            completion_provider: Some(lsp::CompletionOptions {
13969                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13970                resolve_provider: Some(true),
13971                ..Default::default()
13972            }),
13973            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13974            ..Default::default()
13975        },
13976        cx,
13977    )
13978    .await;
13979    let counter = Arc::new(AtomicUsize::new(0));
13980
13981    cx.set_state(indoc! {"
13982        oneˇ
13983        two
13984        three
13985    "});
13986    cx.simulate_keystroke(".");
13987    handle_completion_request(
13988        indoc! {"
13989            one.|<>
13990            two
13991            three
13992        "},
13993        vec!["first_completion", "second_completion"],
13994        true,
13995        counter.clone(),
13996        &mut cx,
13997    )
13998    .await;
13999    cx.condition(|editor, _| editor.context_menu_visible())
14000        .await;
14001    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14002
14003    let _handler = handle_signature_help_request(
14004        &mut cx,
14005        lsp::SignatureHelp {
14006            signatures: vec![lsp::SignatureInformation {
14007                label: "test signature".to_string(),
14008                documentation: None,
14009                parameters: Some(vec![lsp::ParameterInformation {
14010                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
14011                    documentation: None,
14012                }]),
14013                active_parameter: None,
14014            }],
14015            active_signature: None,
14016            active_parameter: None,
14017        },
14018    );
14019    cx.update_editor(|editor, window, cx| {
14020        assert!(
14021            !editor.signature_help_state.is_shown(),
14022            "No signature help was called for"
14023        );
14024        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14025    });
14026    cx.run_until_parked();
14027    cx.update_editor(|editor, _, _| {
14028        assert!(
14029            !editor.signature_help_state.is_shown(),
14030            "No signature help should be shown when completions menu is open"
14031        );
14032    });
14033
14034    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14035        editor.context_menu_next(&Default::default(), window, cx);
14036        editor
14037            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14038            .unwrap()
14039    });
14040    cx.assert_editor_state(indoc! {"
14041        one.second_completionˇ
14042        two
14043        three
14044    "});
14045
14046    handle_resolve_completion_request(
14047        &mut cx,
14048        Some(vec![
14049            (
14050                //This overlaps with the primary completion edit which is
14051                //misbehavior from the LSP spec, test that we filter it out
14052                indoc! {"
14053                    one.second_ˇcompletion
14054                    two
14055                    threeˇ
14056                "},
14057                "overlapping additional edit",
14058            ),
14059            (
14060                indoc! {"
14061                    one.second_completion
14062                    two
14063                    threeˇ
14064                "},
14065                "\nadditional edit",
14066            ),
14067        ]),
14068    )
14069    .await;
14070    apply_additional_edits.await.unwrap();
14071    cx.assert_editor_state(indoc! {"
14072        one.second_completionˇ
14073        two
14074        three
14075        additional edit
14076    "});
14077
14078    cx.set_state(indoc! {"
14079        one.second_completion
14080        twoˇ
14081        threeˇ
14082        additional edit
14083    "});
14084    cx.simulate_keystroke(" ");
14085    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14086    cx.simulate_keystroke("s");
14087    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14088
14089    cx.assert_editor_state(indoc! {"
14090        one.second_completion
14091        two sˇ
14092        three sˇ
14093        additional edit
14094    "});
14095    handle_completion_request(
14096        indoc! {"
14097            one.second_completion
14098            two s
14099            three <s|>
14100            additional edit
14101        "},
14102        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14103        true,
14104        counter.clone(),
14105        &mut cx,
14106    )
14107    .await;
14108    cx.condition(|editor, _| editor.context_menu_visible())
14109        .await;
14110    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14111
14112    cx.simulate_keystroke("i");
14113
14114    handle_completion_request(
14115        indoc! {"
14116            one.second_completion
14117            two si
14118            three <si|>
14119            additional edit
14120        "},
14121        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14122        true,
14123        counter.clone(),
14124        &mut cx,
14125    )
14126    .await;
14127    cx.condition(|editor, _| editor.context_menu_visible())
14128        .await;
14129    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14130
14131    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14132        editor
14133            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14134            .unwrap()
14135    });
14136    cx.assert_editor_state(indoc! {"
14137        one.second_completion
14138        two sixth_completionˇ
14139        three sixth_completionˇ
14140        additional edit
14141    "});
14142
14143    apply_additional_edits.await.unwrap();
14144
14145    update_test_language_settings(&mut cx, |settings| {
14146        settings.defaults.show_completions_on_input = Some(false);
14147    });
14148    cx.set_state("editorˇ");
14149    cx.simulate_keystroke(".");
14150    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14151    cx.simulate_keystrokes("c l o");
14152    cx.assert_editor_state("editor.cloˇ");
14153    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14154    cx.update_editor(|editor, window, cx| {
14155        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14156    });
14157    handle_completion_request(
14158        "editor.<clo|>",
14159        vec!["close", "clobber"],
14160        true,
14161        counter.clone(),
14162        &mut cx,
14163    )
14164    .await;
14165    cx.condition(|editor, _| editor.context_menu_visible())
14166        .await;
14167    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14168
14169    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14170        editor
14171            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14172            .unwrap()
14173    });
14174    cx.assert_editor_state("editor.clobberˇ");
14175    handle_resolve_completion_request(&mut cx, None).await;
14176    apply_additional_edits.await.unwrap();
14177}
14178
14179#[gpui::test]
14180async fn test_completion_reuse(cx: &mut TestAppContext) {
14181    init_test(cx, |_| {});
14182
14183    let mut cx = EditorLspTestContext::new_rust(
14184        lsp::ServerCapabilities {
14185            completion_provider: Some(lsp::CompletionOptions {
14186                trigger_characters: Some(vec![".".to_string()]),
14187                ..Default::default()
14188            }),
14189            ..Default::default()
14190        },
14191        cx,
14192    )
14193    .await;
14194
14195    let counter = Arc::new(AtomicUsize::new(0));
14196    cx.set_state("objˇ");
14197    cx.simulate_keystroke(".");
14198
14199    // Initial completion request returns complete results
14200    let is_incomplete = false;
14201    handle_completion_request(
14202        "obj.|<>",
14203        vec!["a", "ab", "abc"],
14204        is_incomplete,
14205        counter.clone(),
14206        &mut cx,
14207    )
14208    .await;
14209    cx.run_until_parked();
14210    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14211    cx.assert_editor_state("obj.ˇ");
14212    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14213
14214    // Type "a" - filters existing completions
14215    cx.simulate_keystroke("a");
14216    cx.run_until_parked();
14217    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14218    cx.assert_editor_state("obj.aˇ");
14219    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14220
14221    // Type "b" - filters existing completions
14222    cx.simulate_keystroke("b");
14223    cx.run_until_parked();
14224    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14225    cx.assert_editor_state("obj.abˇ");
14226    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14227
14228    // Type "c" - filters existing completions
14229    cx.simulate_keystroke("c");
14230    cx.run_until_parked();
14231    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14232    cx.assert_editor_state("obj.abcˇ");
14233    check_displayed_completions(vec!["abc"], &mut cx);
14234
14235    // Backspace to delete "c" - filters existing completions
14236    cx.update_editor(|editor, window, cx| {
14237        editor.backspace(&Backspace, window, cx);
14238    });
14239    cx.run_until_parked();
14240    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14241    cx.assert_editor_state("obj.abˇ");
14242    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14243
14244    // Moving cursor to the left dismisses menu.
14245    cx.update_editor(|editor, window, cx| {
14246        editor.move_left(&MoveLeft, window, cx);
14247    });
14248    cx.run_until_parked();
14249    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14250    cx.assert_editor_state("obj.aˇb");
14251    cx.update_editor(|editor, _, _| {
14252        assert_eq!(editor.context_menu_visible(), false);
14253    });
14254
14255    // Type "b" - new request
14256    cx.simulate_keystroke("b");
14257    let is_incomplete = false;
14258    handle_completion_request(
14259        "obj.<ab|>a",
14260        vec!["ab", "abc"],
14261        is_incomplete,
14262        counter.clone(),
14263        &mut cx,
14264    )
14265    .await;
14266    cx.run_until_parked();
14267    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14268    cx.assert_editor_state("obj.abˇb");
14269    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14270
14271    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14272    cx.update_editor(|editor, window, cx| {
14273        editor.backspace(&Backspace, window, cx);
14274    });
14275    let is_incomplete = false;
14276    handle_completion_request(
14277        "obj.<a|>b",
14278        vec!["a", "ab", "abc"],
14279        is_incomplete,
14280        counter.clone(),
14281        &mut cx,
14282    )
14283    .await;
14284    cx.run_until_parked();
14285    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14286    cx.assert_editor_state("obj.aˇb");
14287    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14288
14289    // Backspace to delete "a" - dismisses menu.
14290    cx.update_editor(|editor, window, cx| {
14291        editor.backspace(&Backspace, window, cx);
14292    });
14293    cx.run_until_parked();
14294    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14295    cx.assert_editor_state("obj.ˇb");
14296    cx.update_editor(|editor, _, _| {
14297        assert_eq!(editor.context_menu_visible(), false);
14298    });
14299}
14300
14301#[gpui::test]
14302async fn test_word_completion(cx: &mut TestAppContext) {
14303    let lsp_fetch_timeout_ms = 10;
14304    init_test(cx, |language_settings| {
14305        language_settings.defaults.completions = Some(CompletionSettingsContent {
14306            words_min_length: Some(0),
14307            lsp_fetch_timeout_ms: Some(10),
14308            lsp_insert_mode: Some(LspInsertMode::Insert),
14309            ..Default::default()
14310        });
14311    });
14312
14313    let mut cx = EditorLspTestContext::new_rust(
14314        lsp::ServerCapabilities {
14315            completion_provider: Some(lsp::CompletionOptions {
14316                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14317                ..lsp::CompletionOptions::default()
14318            }),
14319            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14320            ..lsp::ServerCapabilities::default()
14321        },
14322        cx,
14323    )
14324    .await;
14325
14326    let throttle_completions = Arc::new(AtomicBool::new(false));
14327
14328    let lsp_throttle_completions = throttle_completions.clone();
14329    let _completion_requests_handler =
14330        cx.lsp
14331            .server
14332            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14333                let lsp_throttle_completions = lsp_throttle_completions.clone();
14334                let cx = cx.clone();
14335                async move {
14336                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14337                        cx.background_executor()
14338                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14339                            .await;
14340                    }
14341                    Ok(Some(lsp::CompletionResponse::Array(vec![
14342                        lsp::CompletionItem {
14343                            label: "first".into(),
14344                            ..lsp::CompletionItem::default()
14345                        },
14346                        lsp::CompletionItem {
14347                            label: "last".into(),
14348                            ..lsp::CompletionItem::default()
14349                        },
14350                    ])))
14351                }
14352            });
14353
14354    cx.set_state(indoc! {"
14355        oneˇ
14356        two
14357        three
14358    "});
14359    cx.simulate_keystroke(".");
14360    cx.executor().run_until_parked();
14361    cx.condition(|editor, _| editor.context_menu_visible())
14362        .await;
14363    cx.update_editor(|editor, window, cx| {
14364        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14365        {
14366            assert_eq!(
14367                completion_menu_entries(menu),
14368                &["first", "last"],
14369                "When LSP server is fast to reply, no fallback word completions are used"
14370            );
14371        } else {
14372            panic!("expected completion menu to be open");
14373        }
14374        editor.cancel(&Cancel, window, cx);
14375    });
14376    cx.executor().run_until_parked();
14377    cx.condition(|editor, _| !editor.context_menu_visible())
14378        .await;
14379
14380    throttle_completions.store(true, atomic::Ordering::Release);
14381    cx.simulate_keystroke(".");
14382    cx.executor()
14383        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14384    cx.executor().run_until_parked();
14385    cx.condition(|editor, _| editor.context_menu_visible())
14386        .await;
14387    cx.update_editor(|editor, _, _| {
14388        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14389        {
14390            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14391                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14392        } else {
14393            panic!("expected completion menu to be open");
14394        }
14395    });
14396}
14397
14398#[gpui::test]
14399async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14400    init_test(cx, |language_settings| {
14401        language_settings.defaults.completions = Some(CompletionSettingsContent {
14402            words: Some(WordsCompletionMode::Enabled),
14403            words_min_length: Some(0),
14404            lsp_insert_mode: Some(LspInsertMode::Insert),
14405            ..Default::default()
14406        });
14407    });
14408
14409    let mut cx = EditorLspTestContext::new_rust(
14410        lsp::ServerCapabilities {
14411            completion_provider: Some(lsp::CompletionOptions {
14412                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14413                ..lsp::CompletionOptions::default()
14414            }),
14415            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14416            ..lsp::ServerCapabilities::default()
14417        },
14418        cx,
14419    )
14420    .await;
14421
14422    let _completion_requests_handler =
14423        cx.lsp
14424            .server
14425            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14426                Ok(Some(lsp::CompletionResponse::Array(vec![
14427                    lsp::CompletionItem {
14428                        label: "first".into(),
14429                        ..lsp::CompletionItem::default()
14430                    },
14431                    lsp::CompletionItem {
14432                        label: "last".into(),
14433                        ..lsp::CompletionItem::default()
14434                    },
14435                ])))
14436            });
14437
14438    cx.set_state(indoc! {"ˇ
14439        first
14440        last
14441        second
14442    "});
14443    cx.simulate_keystroke(".");
14444    cx.executor().run_until_parked();
14445    cx.condition(|editor, _| editor.context_menu_visible())
14446        .await;
14447    cx.update_editor(|editor, _, _| {
14448        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14449        {
14450            assert_eq!(
14451                completion_menu_entries(menu),
14452                &["first", "last", "second"],
14453                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14454            );
14455        } else {
14456            panic!("expected completion menu to be open");
14457        }
14458    });
14459}
14460
14461#[gpui::test]
14462async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14463    init_test(cx, |language_settings| {
14464        language_settings.defaults.completions = Some(CompletionSettingsContent {
14465            words: Some(WordsCompletionMode::Disabled),
14466            words_min_length: Some(0),
14467            lsp_insert_mode: Some(LspInsertMode::Insert),
14468            ..Default::default()
14469        });
14470    });
14471
14472    let mut cx = EditorLspTestContext::new_rust(
14473        lsp::ServerCapabilities {
14474            completion_provider: Some(lsp::CompletionOptions {
14475                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14476                ..lsp::CompletionOptions::default()
14477            }),
14478            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14479            ..lsp::ServerCapabilities::default()
14480        },
14481        cx,
14482    )
14483    .await;
14484
14485    let _completion_requests_handler =
14486        cx.lsp
14487            .server
14488            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14489                panic!("LSP completions should not be queried when dealing with word completions")
14490            });
14491
14492    cx.set_state(indoc! {"ˇ
14493        first
14494        last
14495        second
14496    "});
14497    cx.update_editor(|editor, window, cx| {
14498        editor.show_word_completions(&ShowWordCompletions, window, cx);
14499    });
14500    cx.executor().run_until_parked();
14501    cx.condition(|editor, _| editor.context_menu_visible())
14502        .await;
14503    cx.update_editor(|editor, _, _| {
14504        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14505        {
14506            assert_eq!(
14507                completion_menu_entries(menu),
14508                &["first", "last", "second"],
14509                "`ShowWordCompletions` action should show word completions"
14510            );
14511        } else {
14512            panic!("expected completion menu to be open");
14513        }
14514    });
14515
14516    cx.simulate_keystroke("l");
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                &["last"],
14526                "After showing word completions, further editing should filter them and not query the LSP"
14527            );
14528        } else {
14529            panic!("expected completion menu to be open");
14530        }
14531    });
14532}
14533
14534#[gpui::test]
14535async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14536    init_test(cx, |language_settings| {
14537        language_settings.defaults.completions = Some(CompletionSettingsContent {
14538            words_min_length: Some(0),
14539            lsp: Some(false),
14540            lsp_insert_mode: Some(LspInsertMode::Insert),
14541            ..Default::default()
14542        });
14543    });
14544
14545    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14546
14547    cx.set_state(indoc! {"ˇ
14548        0_usize
14549        let
14550        33
14551        4.5f32
14552    "});
14553    cx.update_editor(|editor, window, cx| {
14554        editor.show_completions(&ShowCompletions::default(), window, cx);
14555    });
14556    cx.executor().run_until_parked();
14557    cx.condition(|editor, _| editor.context_menu_visible())
14558        .await;
14559    cx.update_editor(|editor, window, cx| {
14560        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14561        {
14562            assert_eq!(
14563                completion_menu_entries(menu),
14564                &["let"],
14565                "With no digits in the completion query, no digits should be in the word completions"
14566            );
14567        } else {
14568            panic!("expected completion menu to be open");
14569        }
14570        editor.cancel(&Cancel, window, cx);
14571    });
14572
14573    cx.set_state(indoc! {"14574        0_usize
14575        let
14576        3
14577        33.35f32
14578    "});
14579    cx.update_editor(|editor, window, cx| {
14580        editor.show_completions(&ShowCompletions::default(), window, cx);
14581    });
14582    cx.executor().run_until_parked();
14583    cx.condition(|editor, _| editor.context_menu_visible())
14584        .await;
14585    cx.update_editor(|editor, _, _| {
14586        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14587        {
14588            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14589                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14590        } else {
14591            panic!("expected completion menu to be open");
14592        }
14593    });
14594}
14595
14596#[gpui::test]
14597async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
14598    init_test(cx, |language_settings| {
14599        language_settings.defaults.completions = Some(CompletionSettingsContent {
14600            words: Some(WordsCompletionMode::Enabled),
14601            words_min_length: Some(3),
14602            lsp_insert_mode: Some(LspInsertMode::Insert),
14603            ..Default::default()
14604        });
14605    });
14606
14607    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14608    cx.set_state(indoc! {"ˇ
14609        wow
14610        wowen
14611        wowser
14612    "});
14613    cx.simulate_keystroke("w");
14614    cx.executor().run_until_parked();
14615    cx.update_editor(|editor, _, _| {
14616        if editor.context_menu.borrow_mut().is_some() {
14617            panic!(
14618                "expected completion menu to be hidden, as words completion threshold is not met"
14619            );
14620        }
14621    });
14622
14623    cx.update_editor(|editor, window, cx| {
14624        editor.show_word_completions(&ShowWordCompletions, window, cx);
14625    });
14626    cx.executor().run_until_parked();
14627    cx.update_editor(|editor, window, cx| {
14628        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14629        {
14630            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");
14631        } else {
14632            panic!("expected completion menu to be open after the word completions are called with an action");
14633        }
14634
14635        editor.cancel(&Cancel, window, cx);
14636    });
14637    cx.update_editor(|editor, _, _| {
14638        if editor.context_menu.borrow_mut().is_some() {
14639            panic!("expected completion menu to be hidden after canceling");
14640        }
14641    });
14642
14643    cx.simulate_keystroke("o");
14644    cx.executor().run_until_parked();
14645    cx.update_editor(|editor, _, _| {
14646        if editor.context_menu.borrow_mut().is_some() {
14647            panic!(
14648                "expected completion menu to be hidden, as words completion threshold is not met still"
14649            );
14650        }
14651    });
14652
14653    cx.simulate_keystroke("w");
14654    cx.executor().run_until_parked();
14655    cx.update_editor(|editor, _, _| {
14656        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14657        {
14658            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
14659        } else {
14660            panic!("expected completion menu to be open after the word completions threshold is met");
14661        }
14662    });
14663}
14664
14665#[gpui::test]
14666async fn test_word_completions_disabled(cx: &mut TestAppContext) {
14667    init_test(cx, |language_settings| {
14668        language_settings.defaults.completions = Some(CompletionSettingsContent {
14669            words: Some(WordsCompletionMode::Enabled),
14670            words_min_length: Some(0),
14671            lsp_insert_mode: Some(LspInsertMode::Insert),
14672            ..Default::default()
14673        });
14674    });
14675
14676    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14677    cx.update_editor(|editor, _, _| {
14678        editor.disable_word_completions();
14679    });
14680    cx.set_state(indoc! {"ˇ
14681        wow
14682        wowen
14683        wowser
14684    "});
14685    cx.simulate_keystroke("w");
14686    cx.executor().run_until_parked();
14687    cx.update_editor(|editor, _, _| {
14688        if editor.context_menu.borrow_mut().is_some() {
14689            panic!(
14690                "expected completion menu to be hidden, as words completion are disabled for this editor"
14691            );
14692        }
14693    });
14694
14695    cx.update_editor(|editor, window, cx| {
14696        editor.show_word_completions(&ShowWordCompletions, window, cx);
14697    });
14698    cx.executor().run_until_parked();
14699    cx.update_editor(|editor, _, _| {
14700        if editor.context_menu.borrow_mut().is_some() {
14701            panic!(
14702                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
14703            );
14704        }
14705    });
14706}
14707
14708fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
14709    let position = || lsp::Position {
14710        line: params.text_document_position.position.line,
14711        character: params.text_document_position.position.character,
14712    };
14713    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14714        range: lsp::Range {
14715            start: position(),
14716            end: position(),
14717        },
14718        new_text: text.to_string(),
14719    }))
14720}
14721
14722#[gpui::test]
14723async fn test_multiline_completion(cx: &mut TestAppContext) {
14724    init_test(cx, |_| {});
14725
14726    let fs = FakeFs::new(cx.executor());
14727    fs.insert_tree(
14728        path!("/a"),
14729        json!({
14730            "main.ts": "a",
14731        }),
14732    )
14733    .await;
14734
14735    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14736    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14737    let typescript_language = Arc::new(Language::new(
14738        LanguageConfig {
14739            name: "TypeScript".into(),
14740            matcher: LanguageMatcher {
14741                path_suffixes: vec!["ts".to_string()],
14742                ..LanguageMatcher::default()
14743            },
14744            line_comments: vec!["// ".into()],
14745            ..LanguageConfig::default()
14746        },
14747        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14748    ));
14749    language_registry.add(typescript_language.clone());
14750    let mut fake_servers = language_registry.register_fake_lsp(
14751        "TypeScript",
14752        FakeLspAdapter {
14753            capabilities: lsp::ServerCapabilities {
14754                completion_provider: Some(lsp::CompletionOptions {
14755                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14756                    ..lsp::CompletionOptions::default()
14757                }),
14758                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14759                ..lsp::ServerCapabilities::default()
14760            },
14761            // Emulate vtsls label generation
14762            label_for_completion: Some(Box::new(|item, _| {
14763                let text = if let Some(description) = item
14764                    .label_details
14765                    .as_ref()
14766                    .and_then(|label_details| label_details.description.as_ref())
14767                {
14768                    format!("{} {}", item.label, description)
14769                } else if let Some(detail) = &item.detail {
14770                    format!("{} {}", item.label, detail)
14771                } else {
14772                    item.label.clone()
14773                };
14774                let len = text.len();
14775                Some(language::CodeLabel {
14776                    text,
14777                    runs: Vec::new(),
14778                    filter_range: 0..len,
14779                })
14780            })),
14781            ..FakeLspAdapter::default()
14782        },
14783    );
14784    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14785    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14786    let worktree_id = workspace
14787        .update(cx, |workspace, _window, cx| {
14788            workspace.project().update(cx, |project, cx| {
14789                project.worktrees(cx).next().unwrap().read(cx).id()
14790            })
14791        })
14792        .unwrap();
14793    let _buffer = project
14794        .update(cx, |project, cx| {
14795            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
14796        })
14797        .await
14798        .unwrap();
14799    let editor = workspace
14800        .update(cx, |workspace, window, cx| {
14801            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
14802        })
14803        .unwrap()
14804        .await
14805        .unwrap()
14806        .downcast::<Editor>()
14807        .unwrap();
14808    let fake_server = fake_servers.next().await.unwrap();
14809
14810    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
14811    let multiline_label_2 = "a\nb\nc\n";
14812    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
14813    let multiline_description = "d\ne\nf\n";
14814    let multiline_detail_2 = "g\nh\ni\n";
14815
14816    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
14817        move |params, _| async move {
14818            Ok(Some(lsp::CompletionResponse::Array(vec![
14819                lsp::CompletionItem {
14820                    label: multiline_label.to_string(),
14821                    text_edit: gen_text_edit(&params, "new_text_1"),
14822                    ..lsp::CompletionItem::default()
14823                },
14824                lsp::CompletionItem {
14825                    label: "single line label 1".to_string(),
14826                    detail: Some(multiline_detail.to_string()),
14827                    text_edit: gen_text_edit(&params, "new_text_2"),
14828                    ..lsp::CompletionItem::default()
14829                },
14830                lsp::CompletionItem {
14831                    label: "single line label 2".to_string(),
14832                    label_details: Some(lsp::CompletionItemLabelDetails {
14833                        description: Some(multiline_description.to_string()),
14834                        detail: None,
14835                    }),
14836                    text_edit: gen_text_edit(&params, "new_text_2"),
14837                    ..lsp::CompletionItem::default()
14838                },
14839                lsp::CompletionItem {
14840                    label: multiline_label_2.to_string(),
14841                    detail: Some(multiline_detail_2.to_string()),
14842                    text_edit: gen_text_edit(&params, "new_text_3"),
14843                    ..lsp::CompletionItem::default()
14844                },
14845                lsp::CompletionItem {
14846                    label: "Label with many     spaces and \t but without newlines".to_string(),
14847                    detail: Some(
14848                        "Details with many     spaces and \t but without newlines".to_string(),
14849                    ),
14850                    text_edit: gen_text_edit(&params, "new_text_4"),
14851                    ..lsp::CompletionItem::default()
14852                },
14853            ])))
14854        },
14855    );
14856
14857    editor.update_in(cx, |editor, window, cx| {
14858        cx.focus_self(window);
14859        editor.move_to_end(&MoveToEnd, window, cx);
14860        editor.handle_input(".", window, cx);
14861    });
14862    cx.run_until_parked();
14863    completion_handle.next().await.unwrap();
14864
14865    editor.update(cx, |editor, _| {
14866        assert!(editor.context_menu_visible());
14867        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14868        {
14869            let completion_labels = menu
14870                .completions
14871                .borrow()
14872                .iter()
14873                .map(|c| c.label.text.clone())
14874                .collect::<Vec<_>>();
14875            assert_eq!(
14876                completion_labels,
14877                &[
14878                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
14879                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
14880                    "single line label 2 d e f ",
14881                    "a b c g h i ",
14882                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
14883                ],
14884                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
14885            );
14886
14887            for completion in menu
14888                .completions
14889                .borrow()
14890                .iter() {
14891                    assert_eq!(
14892                        completion.label.filter_range,
14893                        0..completion.label.text.len(),
14894                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
14895                    );
14896                }
14897        } else {
14898            panic!("expected completion menu to be open");
14899        }
14900    });
14901}
14902
14903#[gpui::test]
14904async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
14905    init_test(cx, |_| {});
14906    let mut cx = EditorLspTestContext::new_rust(
14907        lsp::ServerCapabilities {
14908            completion_provider: Some(lsp::CompletionOptions {
14909                trigger_characters: Some(vec![".".to_string()]),
14910                ..Default::default()
14911            }),
14912            ..Default::default()
14913        },
14914        cx,
14915    )
14916    .await;
14917    cx.lsp
14918        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14919            Ok(Some(lsp::CompletionResponse::Array(vec![
14920                lsp::CompletionItem {
14921                    label: "first".into(),
14922                    ..Default::default()
14923                },
14924                lsp::CompletionItem {
14925                    label: "last".into(),
14926                    ..Default::default()
14927                },
14928            ])))
14929        });
14930    cx.set_state("variableˇ");
14931    cx.simulate_keystroke(".");
14932    cx.executor().run_until_parked();
14933
14934    cx.update_editor(|editor, _, _| {
14935        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14936        {
14937            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
14938        } else {
14939            panic!("expected completion menu to be open");
14940        }
14941    });
14942
14943    cx.update_editor(|editor, window, cx| {
14944        editor.move_page_down(&MovePageDown::default(), window, cx);
14945        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14946        {
14947            assert!(
14948                menu.selected_item == 1,
14949                "expected PageDown to select the last item from the context menu"
14950            );
14951        } else {
14952            panic!("expected completion menu to stay open after PageDown");
14953        }
14954    });
14955
14956    cx.update_editor(|editor, window, cx| {
14957        editor.move_page_up(&MovePageUp::default(), window, cx);
14958        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14959        {
14960            assert!(
14961                menu.selected_item == 0,
14962                "expected PageUp to select the first item from the context menu"
14963            );
14964        } else {
14965            panic!("expected completion menu to stay open after PageUp");
14966        }
14967    });
14968}
14969
14970#[gpui::test]
14971async fn test_as_is_completions(cx: &mut TestAppContext) {
14972    init_test(cx, |_| {});
14973    let mut cx = EditorLspTestContext::new_rust(
14974        lsp::ServerCapabilities {
14975            completion_provider: Some(lsp::CompletionOptions {
14976                ..Default::default()
14977            }),
14978            ..Default::default()
14979        },
14980        cx,
14981    )
14982    .await;
14983    cx.lsp
14984        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14985            Ok(Some(lsp::CompletionResponse::Array(vec![
14986                lsp::CompletionItem {
14987                    label: "unsafe".into(),
14988                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14989                        range: lsp::Range {
14990                            start: lsp::Position {
14991                                line: 1,
14992                                character: 2,
14993                            },
14994                            end: lsp::Position {
14995                                line: 1,
14996                                character: 3,
14997                            },
14998                        },
14999                        new_text: "unsafe".to_string(),
15000                    })),
15001                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
15002                    ..Default::default()
15003                },
15004            ])))
15005        });
15006    cx.set_state("fn a() {}\n");
15007    cx.executor().run_until_parked();
15008    cx.update_editor(|editor, window, cx| {
15009        editor.show_completions(
15010            &ShowCompletions {
15011                trigger: Some("\n".into()),
15012            },
15013            window,
15014            cx,
15015        );
15016    });
15017    cx.executor().run_until_parked();
15018
15019    cx.update_editor(|editor, window, cx| {
15020        editor.confirm_completion(&Default::default(), window, cx)
15021    });
15022    cx.executor().run_until_parked();
15023    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
15024}
15025
15026#[gpui::test]
15027async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
15028    init_test(cx, |_| {});
15029    let language =
15030        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
15031    let mut cx = EditorLspTestContext::new(
15032        language,
15033        lsp::ServerCapabilities {
15034            completion_provider: Some(lsp::CompletionOptions {
15035                ..lsp::CompletionOptions::default()
15036            }),
15037            ..lsp::ServerCapabilities::default()
15038        },
15039        cx,
15040    )
15041    .await;
15042
15043    cx.set_state(
15044        "#ifndef BAR_H
15045#define BAR_H
15046
15047#include <stdbool.h>
15048
15049int fn_branch(bool do_branch1, bool do_branch2);
15050
15051#endif // BAR_H
15052ˇ",
15053    );
15054    cx.executor().run_until_parked();
15055    cx.update_editor(|editor, window, cx| {
15056        editor.handle_input("#", window, cx);
15057    });
15058    cx.executor().run_until_parked();
15059    cx.update_editor(|editor, window, cx| {
15060        editor.handle_input("i", window, cx);
15061    });
15062    cx.executor().run_until_parked();
15063    cx.update_editor(|editor, window, cx| {
15064        editor.handle_input("n", window, cx);
15065    });
15066    cx.executor().run_until_parked();
15067    cx.assert_editor_state(
15068        "#ifndef BAR_H
15069#define BAR_H
15070
15071#include <stdbool.h>
15072
15073int fn_branch(bool do_branch1, bool do_branch2);
15074
15075#endif // BAR_H
15076#inˇ",
15077    );
15078
15079    cx.lsp
15080        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15081            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15082                is_incomplete: false,
15083                item_defaults: None,
15084                items: vec![lsp::CompletionItem {
15085                    kind: Some(lsp::CompletionItemKind::SNIPPET),
15086                    label_details: Some(lsp::CompletionItemLabelDetails {
15087                        detail: Some("header".to_string()),
15088                        description: None,
15089                    }),
15090                    label: " include".to_string(),
15091                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15092                        range: lsp::Range {
15093                            start: lsp::Position {
15094                                line: 8,
15095                                character: 1,
15096                            },
15097                            end: lsp::Position {
15098                                line: 8,
15099                                character: 1,
15100                            },
15101                        },
15102                        new_text: "include \"$0\"".to_string(),
15103                    })),
15104                    sort_text: Some("40b67681include".to_string()),
15105                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15106                    filter_text: Some("include".to_string()),
15107                    insert_text: Some("include \"$0\"".to_string()),
15108                    ..lsp::CompletionItem::default()
15109                }],
15110            })))
15111        });
15112    cx.update_editor(|editor, window, cx| {
15113        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15114    });
15115    cx.executor().run_until_parked();
15116    cx.update_editor(|editor, window, cx| {
15117        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15118    });
15119    cx.executor().run_until_parked();
15120    cx.assert_editor_state(
15121        "#ifndef BAR_H
15122#define BAR_H
15123
15124#include <stdbool.h>
15125
15126int fn_branch(bool do_branch1, bool do_branch2);
15127
15128#endif // BAR_H
15129#include \"ˇ\"",
15130    );
15131
15132    cx.lsp
15133        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15134            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15135                is_incomplete: true,
15136                item_defaults: None,
15137                items: vec![lsp::CompletionItem {
15138                    kind: Some(lsp::CompletionItemKind::FILE),
15139                    label: "AGL/".to_string(),
15140                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15141                        range: lsp::Range {
15142                            start: lsp::Position {
15143                                line: 8,
15144                                character: 10,
15145                            },
15146                            end: lsp::Position {
15147                                line: 8,
15148                                character: 11,
15149                            },
15150                        },
15151                        new_text: "AGL/".to_string(),
15152                    })),
15153                    sort_text: Some("40b67681AGL/".to_string()),
15154                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15155                    filter_text: Some("AGL/".to_string()),
15156                    insert_text: Some("AGL/".to_string()),
15157                    ..lsp::CompletionItem::default()
15158                }],
15159            })))
15160        });
15161    cx.update_editor(|editor, window, cx| {
15162        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15163    });
15164    cx.executor().run_until_parked();
15165    cx.update_editor(|editor, window, cx| {
15166        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15167    });
15168    cx.executor().run_until_parked();
15169    cx.assert_editor_state(
15170        r##"#ifndef BAR_H
15171#define BAR_H
15172
15173#include <stdbool.h>
15174
15175int fn_branch(bool do_branch1, bool do_branch2);
15176
15177#endif // BAR_H
15178#include "AGL/ˇ"##,
15179    );
15180
15181    cx.update_editor(|editor, window, cx| {
15182        editor.handle_input("\"", window, cx);
15183    });
15184    cx.executor().run_until_parked();
15185    cx.assert_editor_state(
15186        r##"#ifndef BAR_H
15187#define BAR_H
15188
15189#include <stdbool.h>
15190
15191int fn_branch(bool do_branch1, bool do_branch2);
15192
15193#endif // BAR_H
15194#include "AGL/"ˇ"##,
15195    );
15196}
15197
15198#[gpui::test]
15199async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15200    init_test(cx, |_| {});
15201
15202    let mut cx = EditorLspTestContext::new_rust(
15203        lsp::ServerCapabilities {
15204            completion_provider: Some(lsp::CompletionOptions {
15205                trigger_characters: Some(vec![".".to_string()]),
15206                resolve_provider: Some(true),
15207                ..Default::default()
15208            }),
15209            ..Default::default()
15210        },
15211        cx,
15212    )
15213    .await;
15214
15215    cx.set_state("fn main() { let a = 2ˇ; }");
15216    cx.simulate_keystroke(".");
15217    let completion_item = lsp::CompletionItem {
15218        label: "Some".into(),
15219        kind: Some(lsp::CompletionItemKind::SNIPPET),
15220        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15221        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15222            kind: lsp::MarkupKind::Markdown,
15223            value: "```rust\nSome(2)\n```".to_string(),
15224        })),
15225        deprecated: Some(false),
15226        sort_text: Some("Some".to_string()),
15227        filter_text: Some("Some".to_string()),
15228        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15229        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15230            range: lsp::Range {
15231                start: lsp::Position {
15232                    line: 0,
15233                    character: 22,
15234                },
15235                end: lsp::Position {
15236                    line: 0,
15237                    character: 22,
15238                },
15239            },
15240            new_text: "Some(2)".to_string(),
15241        })),
15242        additional_text_edits: Some(vec![lsp::TextEdit {
15243            range: lsp::Range {
15244                start: lsp::Position {
15245                    line: 0,
15246                    character: 20,
15247                },
15248                end: lsp::Position {
15249                    line: 0,
15250                    character: 22,
15251                },
15252            },
15253            new_text: "".to_string(),
15254        }]),
15255        ..Default::default()
15256    };
15257
15258    let closure_completion_item = completion_item.clone();
15259    let counter = Arc::new(AtomicUsize::new(0));
15260    let counter_clone = counter.clone();
15261    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15262        let task_completion_item = closure_completion_item.clone();
15263        counter_clone.fetch_add(1, atomic::Ordering::Release);
15264        async move {
15265            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15266                is_incomplete: true,
15267                item_defaults: None,
15268                items: vec![task_completion_item],
15269            })))
15270        }
15271    });
15272
15273    cx.condition(|editor, _| editor.context_menu_visible())
15274        .await;
15275    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15276    assert!(request.next().await.is_some());
15277    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15278
15279    cx.simulate_keystrokes("S o m");
15280    cx.condition(|editor, _| editor.context_menu_visible())
15281        .await;
15282    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15283    assert!(request.next().await.is_some());
15284    assert!(request.next().await.is_some());
15285    assert!(request.next().await.is_some());
15286    request.close();
15287    assert!(request.next().await.is_none());
15288    assert_eq!(
15289        counter.load(atomic::Ordering::Acquire),
15290        4,
15291        "With the completions menu open, only one LSP request should happen per input"
15292    );
15293}
15294
15295#[gpui::test]
15296async fn test_toggle_comment(cx: &mut TestAppContext) {
15297    init_test(cx, |_| {});
15298    let mut cx = EditorTestContext::new(cx).await;
15299    let language = Arc::new(Language::new(
15300        LanguageConfig {
15301            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15302            ..Default::default()
15303        },
15304        Some(tree_sitter_rust::LANGUAGE.into()),
15305    ));
15306    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15307
15308    // If multiple selections intersect a line, the line is only toggled once.
15309    cx.set_state(indoc! {"
15310        fn a() {
15311            «//b();
15312            ˇ»// «c();
15313            //ˇ»  d();
15314        }
15315    "});
15316
15317    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15318
15319    cx.assert_editor_state(indoc! {"
15320        fn a() {
15321            «b();
15322            c();
15323            ˇ» d();
15324        }
15325    "});
15326
15327    // The comment prefix is inserted at the same column for every line in a
15328    // selection.
15329    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15330
15331    cx.assert_editor_state(indoc! {"
15332        fn a() {
15333            // «b();
15334            // c();
15335            ˇ»//  d();
15336        }
15337    "});
15338
15339    // If a selection ends at the beginning of a line, that line is not toggled.
15340    cx.set_selections_state(indoc! {"
15341        fn a() {
15342            // b();
15343            «// c();
15344        ˇ»    //  d();
15345        }
15346    "});
15347
15348    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15349
15350    cx.assert_editor_state(indoc! {"
15351        fn a() {
15352            // b();
15353            «c();
15354        ˇ»    //  d();
15355        }
15356    "});
15357
15358    // If a selection span a single line and is empty, the line is toggled.
15359    cx.set_state(indoc! {"
15360        fn a() {
15361            a();
15362            b();
15363        ˇ
15364        }
15365    "});
15366
15367    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15368
15369    cx.assert_editor_state(indoc! {"
15370        fn a() {
15371            a();
15372            b();
15373        //•ˇ
15374        }
15375    "});
15376
15377    // If a selection span multiple lines, empty lines are not toggled.
15378    cx.set_state(indoc! {"
15379        fn a() {
15380            «a();
15381
15382            c();ˇ»
15383        }
15384    "});
15385
15386    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15387
15388    cx.assert_editor_state(indoc! {"
15389        fn a() {
15390            // «a();
15391
15392            // c();ˇ»
15393        }
15394    "});
15395
15396    // If a selection includes multiple comment prefixes, all lines are uncommented.
15397    cx.set_state(indoc! {"
15398        fn a() {
15399            «// a();
15400            /// b();
15401            //! c();ˇ»
15402        }
15403    "});
15404
15405    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15406
15407    cx.assert_editor_state(indoc! {"
15408        fn a() {
15409            «a();
15410            b();
15411            c();ˇ»
15412        }
15413    "});
15414}
15415
15416#[gpui::test]
15417async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15418    init_test(cx, |_| {});
15419    let mut cx = EditorTestContext::new(cx).await;
15420    let language = Arc::new(Language::new(
15421        LanguageConfig {
15422            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15423            ..Default::default()
15424        },
15425        Some(tree_sitter_rust::LANGUAGE.into()),
15426    ));
15427    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15428
15429    let toggle_comments = &ToggleComments {
15430        advance_downwards: false,
15431        ignore_indent: true,
15432    };
15433
15434    // If multiple selections intersect a line, the line is only toggled once.
15435    cx.set_state(indoc! {"
15436        fn a() {
15437        //    «b();
15438        //    c();
15439        //    ˇ» d();
15440        }
15441    "});
15442
15443    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15444
15445    cx.assert_editor_state(indoc! {"
15446        fn a() {
15447            «b();
15448            c();
15449            ˇ» d();
15450        }
15451    "});
15452
15453    // The comment prefix is inserted at the beginning of each line
15454    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15455
15456    cx.assert_editor_state(indoc! {"
15457        fn a() {
15458        //    «b();
15459        //    c();
15460        //    ˇ» d();
15461        }
15462    "});
15463
15464    // If a selection ends at the beginning of a line, that line is not toggled.
15465    cx.set_selections_state(indoc! {"
15466        fn a() {
15467        //    b();
15468        //    «c();
15469        ˇ»//     d();
15470        }
15471    "});
15472
15473    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15474
15475    cx.assert_editor_state(indoc! {"
15476        fn a() {
15477        //    b();
15478            «c();
15479        ˇ»//     d();
15480        }
15481    "});
15482
15483    // If a selection span a single line and is empty, the line is toggled.
15484    cx.set_state(indoc! {"
15485        fn a() {
15486            a();
15487            b();
15488        ˇ
15489        }
15490    "});
15491
15492    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15493
15494    cx.assert_editor_state(indoc! {"
15495        fn a() {
15496            a();
15497            b();
15498        //ˇ
15499        }
15500    "});
15501
15502    // If a selection span multiple lines, empty lines are not toggled.
15503    cx.set_state(indoc! {"
15504        fn a() {
15505            «a();
15506
15507            c();ˇ»
15508        }
15509    "});
15510
15511    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15512
15513    cx.assert_editor_state(indoc! {"
15514        fn a() {
15515        //    «a();
15516
15517        //    c();ˇ»
15518        }
15519    "});
15520
15521    // If a selection includes multiple comment prefixes, all lines are uncommented.
15522    cx.set_state(indoc! {"
15523        fn a() {
15524        //    «a();
15525        ///    b();
15526        //!    c();ˇ»
15527        }
15528    "});
15529
15530    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15531
15532    cx.assert_editor_state(indoc! {"
15533        fn a() {
15534            «a();
15535            b();
15536            c();ˇ»
15537        }
15538    "});
15539}
15540
15541#[gpui::test]
15542async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15543    init_test(cx, |_| {});
15544
15545    let language = Arc::new(Language::new(
15546        LanguageConfig {
15547            line_comments: vec!["// ".into()],
15548            ..Default::default()
15549        },
15550        Some(tree_sitter_rust::LANGUAGE.into()),
15551    ));
15552
15553    let mut cx = EditorTestContext::new(cx).await;
15554
15555    cx.language_registry().add(language.clone());
15556    cx.update_buffer(|buffer, cx| {
15557        buffer.set_language(Some(language), cx);
15558    });
15559
15560    let toggle_comments = &ToggleComments {
15561        advance_downwards: true,
15562        ignore_indent: false,
15563    };
15564
15565    // Single cursor on one line -> advance
15566    // Cursor moves horizontally 3 characters as well on non-blank line
15567    cx.set_state(indoc!(
15568        "fn a() {
15569             ˇdog();
15570             cat();
15571        }"
15572    ));
15573    cx.update_editor(|editor, window, cx| {
15574        editor.toggle_comments(toggle_comments, window, cx);
15575    });
15576    cx.assert_editor_state(indoc!(
15577        "fn a() {
15578             // dog();
15579             catˇ();
15580        }"
15581    ));
15582
15583    // Single selection on one line -> don't advance
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    // Multiple cursors on one line -> advance
15601    cx.set_state(indoc!(
15602        "fn a() {
15603             ˇdˇog();
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, with selection -> don't 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             // ˇdˇog«()ˇ»;
15630             cat();
15631        }"
15632    ));
15633
15634    // Single cursor on one line -> advance
15635    // Cursor moves to column 0 on blank line
15636    cx.set_state(indoc!(
15637        "fn a() {
15638             ˇdog();
15639
15640             cat();
15641        }"
15642    ));
15643    cx.update_editor(|editor, window, cx| {
15644        editor.toggle_comments(toggle_comments, window, cx);
15645    });
15646    cx.assert_editor_state(indoc!(
15647        "fn a() {
15648             // dog();
15649        ˇ
15650             cat();
15651        }"
15652    ));
15653
15654    // Single cursor on one line -> advance
15655    // Cursor starts and ends at column 0
15656    cx.set_state(indoc!(
15657        "fn a() {
15658         ˇ    dog();
15659             cat();
15660        }"
15661    ));
15662    cx.update_editor(|editor, window, cx| {
15663        editor.toggle_comments(toggle_comments, window, cx);
15664    });
15665    cx.assert_editor_state(indoc!(
15666        "fn a() {
15667             // dog();
15668         ˇ    cat();
15669        }"
15670    ));
15671}
15672
15673#[gpui::test]
15674async fn test_toggle_block_comment(cx: &mut TestAppContext) {
15675    init_test(cx, |_| {});
15676
15677    let mut cx = EditorTestContext::new(cx).await;
15678
15679    let html_language = Arc::new(
15680        Language::new(
15681            LanguageConfig {
15682                name: "HTML".into(),
15683                block_comment: Some(BlockCommentConfig {
15684                    start: "<!-- ".into(),
15685                    prefix: "".into(),
15686                    end: " -->".into(),
15687                    tab_size: 0,
15688                }),
15689                ..Default::default()
15690            },
15691            Some(tree_sitter_html::LANGUAGE.into()),
15692        )
15693        .with_injection_query(
15694            r#"
15695            (script_element
15696                (raw_text) @injection.content
15697                (#set! injection.language "javascript"))
15698            "#,
15699        )
15700        .unwrap(),
15701    );
15702
15703    let javascript_language = Arc::new(Language::new(
15704        LanguageConfig {
15705            name: "JavaScript".into(),
15706            line_comments: vec!["// ".into()],
15707            ..Default::default()
15708        },
15709        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15710    ));
15711
15712    cx.language_registry().add(html_language.clone());
15713    cx.language_registry().add(javascript_language);
15714    cx.update_buffer(|buffer, cx| {
15715        buffer.set_language(Some(html_language), cx);
15716    });
15717
15718    // Toggle comments for empty selections
15719    cx.set_state(
15720        &r#"
15721            <p>A</p>ˇ
15722            <p>B</p>ˇ
15723            <p>C</p>ˇ
15724        "#
15725        .unindent(),
15726    );
15727    cx.update_editor(|editor, window, cx| {
15728        editor.toggle_comments(&ToggleComments::default(), window, cx)
15729    });
15730    cx.assert_editor_state(
15731        &r#"
15732            <!-- <p>A</p>ˇ -->
15733            <!-- <p>B</p>ˇ -->
15734            <!-- <p>C</p>ˇ -->
15735        "#
15736        .unindent(),
15737    );
15738    cx.update_editor(|editor, window, cx| {
15739        editor.toggle_comments(&ToggleComments::default(), window, cx)
15740    });
15741    cx.assert_editor_state(
15742        &r#"
15743            <p>A</p>ˇ
15744            <p>B</p>ˇ
15745            <p>C</p>ˇ
15746        "#
15747        .unindent(),
15748    );
15749
15750    // Toggle comments for mixture of empty and non-empty selections, where
15751    // multiple selections occupy a given line.
15752    cx.set_state(
15753        &r#"
15754            <p>A«</p>
15755            <p>ˇ»B</p>ˇ
15756            <p>C«</p>
15757            <p>ˇ»D</p>ˇ
15758        "#
15759        .unindent(),
15760    );
15761
15762    cx.update_editor(|editor, window, cx| {
15763        editor.toggle_comments(&ToggleComments::default(), window, cx)
15764    });
15765    cx.assert_editor_state(
15766        &r#"
15767            <!-- <p>A«</p>
15768            <p>ˇ»B</p>ˇ -->
15769            <!-- <p>C«</p>
15770            <p>ˇ»D</p>ˇ -->
15771        "#
15772        .unindent(),
15773    );
15774    cx.update_editor(|editor, window, cx| {
15775        editor.toggle_comments(&ToggleComments::default(), window, cx)
15776    });
15777    cx.assert_editor_state(
15778        &r#"
15779            <p>A«</p>
15780            <p>ˇ»B</p>ˇ
15781            <p>C«</p>
15782            <p>ˇ»D</p>ˇ
15783        "#
15784        .unindent(),
15785    );
15786
15787    // Toggle comments when different languages are active for different
15788    // selections.
15789    cx.set_state(
15790        &r#"
15791            ˇ<script>
15792                ˇvar x = new Y();
15793            ˇ</script>
15794        "#
15795        .unindent(),
15796    );
15797    cx.executor().run_until_parked();
15798    cx.update_editor(|editor, window, cx| {
15799        editor.toggle_comments(&ToggleComments::default(), window, cx)
15800    });
15801    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
15802    // Uncommenting and commenting from this position brings in even more wrong artifacts.
15803    cx.assert_editor_state(
15804        &r#"
15805            <!-- ˇ<script> -->
15806                // ˇvar x = new Y();
15807            <!-- ˇ</script> -->
15808        "#
15809        .unindent(),
15810    );
15811}
15812
15813#[gpui::test]
15814fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
15815    init_test(cx, |_| {});
15816
15817    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15818    let multibuffer = cx.new(|cx| {
15819        let mut multibuffer = MultiBuffer::new(ReadWrite);
15820        multibuffer.push_excerpts(
15821            buffer.clone(),
15822            [
15823                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
15824                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
15825            ],
15826            cx,
15827        );
15828        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
15829        multibuffer
15830    });
15831
15832    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15833    editor.update_in(cx, |editor, window, cx| {
15834        assert_eq!(editor.text(cx), "aaaa\nbbbb");
15835        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15836            s.select_ranges([
15837                Point::new(0, 0)..Point::new(0, 0),
15838                Point::new(1, 0)..Point::new(1, 0),
15839            ])
15840        });
15841
15842        editor.handle_input("X", window, cx);
15843        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
15844        assert_eq!(
15845            editor.selections.ranges(cx),
15846            [
15847                Point::new(0, 1)..Point::new(0, 1),
15848                Point::new(1, 1)..Point::new(1, 1),
15849            ]
15850        );
15851
15852        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
15853        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15854            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
15855        });
15856        editor.backspace(&Default::default(), window, cx);
15857        assert_eq!(editor.text(cx), "Xa\nbbb");
15858        assert_eq!(
15859            editor.selections.ranges(cx),
15860            [Point::new(1, 0)..Point::new(1, 0)]
15861        );
15862
15863        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15864            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
15865        });
15866        editor.backspace(&Default::default(), window, cx);
15867        assert_eq!(editor.text(cx), "X\nbb");
15868        assert_eq!(
15869            editor.selections.ranges(cx),
15870            [Point::new(0, 1)..Point::new(0, 1)]
15871        );
15872    });
15873}
15874
15875#[gpui::test]
15876fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
15877    init_test(cx, |_| {});
15878
15879    let markers = vec![('[', ']').into(), ('(', ')').into()];
15880    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
15881        indoc! {"
15882            [aaaa
15883            (bbbb]
15884            cccc)",
15885        },
15886        markers.clone(),
15887    );
15888    let excerpt_ranges = markers.into_iter().map(|marker| {
15889        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
15890        ExcerptRange::new(context)
15891    });
15892    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
15893    let multibuffer = cx.new(|cx| {
15894        let mut multibuffer = MultiBuffer::new(ReadWrite);
15895        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
15896        multibuffer
15897    });
15898
15899    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15900    editor.update_in(cx, |editor, window, cx| {
15901        let (expected_text, selection_ranges) = marked_text_ranges(
15902            indoc! {"
15903                aaaa
15904                bˇbbb
15905                bˇbbˇb
15906                cccc"
15907            },
15908            true,
15909        );
15910        assert_eq!(editor.text(cx), expected_text);
15911        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15912            s.select_ranges(selection_ranges)
15913        });
15914
15915        editor.handle_input("X", window, cx);
15916
15917        let (expected_text, expected_selections) = marked_text_ranges(
15918            indoc! {"
15919                aaaa
15920                bXˇbbXb
15921                bXˇbbXˇb
15922                cccc"
15923            },
15924            false,
15925        );
15926        assert_eq!(editor.text(cx), expected_text);
15927        assert_eq!(editor.selections.ranges(cx), expected_selections);
15928
15929        editor.newline(&Newline, window, cx);
15930        let (expected_text, expected_selections) = marked_text_ranges(
15931            indoc! {"
15932                aaaa
15933                bX
15934                ˇbbX
15935                b
15936                bX
15937                ˇbbX
15938                ˇ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}
15947
15948#[gpui::test]
15949fn test_refresh_selections(cx: &mut TestAppContext) {
15950    init_test(cx, |_| {});
15951
15952    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15953    let mut excerpt1_id = None;
15954    let multibuffer = cx.new(|cx| {
15955        let mut multibuffer = MultiBuffer::new(ReadWrite);
15956        excerpt1_id = multibuffer
15957            .push_excerpts(
15958                buffer.clone(),
15959                [
15960                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15961                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15962                ],
15963                cx,
15964            )
15965            .into_iter()
15966            .next();
15967        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15968        multibuffer
15969    });
15970
15971    let editor = cx.add_window(|window, cx| {
15972        let mut editor = build_editor(multibuffer.clone(), window, cx);
15973        let snapshot = editor.snapshot(window, cx);
15974        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15975            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
15976        });
15977        editor.begin_selection(
15978            Point::new(2, 1).to_display_point(&snapshot),
15979            true,
15980            1,
15981            window,
15982            cx,
15983        );
15984        assert_eq!(
15985            editor.selections.ranges(cx),
15986            [
15987                Point::new(1, 3)..Point::new(1, 3),
15988                Point::new(2, 1)..Point::new(2, 1),
15989            ]
15990        );
15991        editor
15992    });
15993
15994    // Refreshing selections is a no-op when excerpts haven't changed.
15995    _ = editor.update(cx, |editor, window, cx| {
15996        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15997        assert_eq!(
15998            editor.selections.ranges(cx),
15999            [
16000                Point::new(1, 3)..Point::new(1, 3),
16001                Point::new(2, 1)..Point::new(2, 1),
16002            ]
16003        );
16004    });
16005
16006    multibuffer.update(cx, |multibuffer, cx| {
16007        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16008    });
16009    _ = editor.update(cx, |editor, window, cx| {
16010        // Removing an excerpt causes the first selection to become degenerate.
16011        assert_eq!(
16012            editor.selections.ranges(cx),
16013            [
16014                Point::new(0, 0)..Point::new(0, 0),
16015                Point::new(0, 1)..Point::new(0, 1)
16016            ]
16017        );
16018
16019        // Refreshing selections will relocate the first selection to the original buffer
16020        // location.
16021        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16022        assert_eq!(
16023            editor.selections.ranges(cx),
16024            [
16025                Point::new(0, 1)..Point::new(0, 1),
16026                Point::new(0, 3)..Point::new(0, 3)
16027            ]
16028        );
16029        assert!(editor.selections.pending_anchor().is_some());
16030    });
16031}
16032
16033#[gpui::test]
16034fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
16035    init_test(cx, |_| {});
16036
16037    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16038    let mut excerpt1_id = None;
16039    let multibuffer = cx.new(|cx| {
16040        let mut multibuffer = MultiBuffer::new(ReadWrite);
16041        excerpt1_id = multibuffer
16042            .push_excerpts(
16043                buffer.clone(),
16044                [
16045                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16046                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16047                ],
16048                cx,
16049            )
16050            .into_iter()
16051            .next();
16052        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16053        multibuffer
16054    });
16055
16056    let editor = cx.add_window(|window, cx| {
16057        let mut editor = build_editor(multibuffer.clone(), window, cx);
16058        let snapshot = editor.snapshot(window, cx);
16059        editor.begin_selection(
16060            Point::new(1, 3).to_display_point(&snapshot),
16061            false,
16062            1,
16063            window,
16064            cx,
16065        );
16066        assert_eq!(
16067            editor.selections.ranges(cx),
16068            [Point::new(1, 3)..Point::new(1, 3)]
16069        );
16070        editor
16071    });
16072
16073    multibuffer.update(cx, |multibuffer, cx| {
16074        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16075    });
16076    _ = editor.update(cx, |editor, window, cx| {
16077        assert_eq!(
16078            editor.selections.ranges(cx),
16079            [Point::new(0, 0)..Point::new(0, 0)]
16080        );
16081
16082        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
16083        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16084        assert_eq!(
16085            editor.selections.ranges(cx),
16086            [Point::new(0, 3)..Point::new(0, 3)]
16087        );
16088        assert!(editor.selections.pending_anchor().is_some());
16089    });
16090}
16091
16092#[gpui::test]
16093async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
16094    init_test(cx, |_| {});
16095
16096    let language = Arc::new(
16097        Language::new(
16098            LanguageConfig {
16099                brackets: BracketPairConfig {
16100                    pairs: vec![
16101                        BracketPair {
16102                            start: "{".to_string(),
16103                            end: "}".to_string(),
16104                            close: true,
16105                            surround: true,
16106                            newline: true,
16107                        },
16108                        BracketPair {
16109                            start: "/* ".to_string(),
16110                            end: " */".to_string(),
16111                            close: true,
16112                            surround: true,
16113                            newline: true,
16114                        },
16115                    ],
16116                    ..Default::default()
16117                },
16118                ..Default::default()
16119            },
16120            Some(tree_sitter_rust::LANGUAGE.into()),
16121        )
16122        .with_indents_query("")
16123        .unwrap(),
16124    );
16125
16126    let text = concat!(
16127        "{   }\n",     //
16128        "  x\n",       //
16129        "  /*   */\n", //
16130        "x\n",         //
16131        "{{} }\n",     //
16132    );
16133
16134    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16135    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16136    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16137    editor
16138        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16139        .await;
16140
16141    editor.update_in(cx, |editor, window, cx| {
16142        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16143            s.select_display_ranges([
16144                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16145                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16146                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16147            ])
16148        });
16149        editor.newline(&Newline, window, cx);
16150
16151        assert_eq!(
16152            editor.buffer().read(cx).read(cx).text(),
16153            concat!(
16154                "{ \n",    // Suppress rustfmt
16155                "\n",      //
16156                "}\n",     //
16157                "  x\n",   //
16158                "  /* \n", //
16159                "  \n",    //
16160                "  */\n",  //
16161                "x\n",     //
16162                "{{} \n",  //
16163                "}\n",     //
16164            )
16165        );
16166    });
16167}
16168
16169#[gpui::test]
16170fn test_highlighted_ranges(cx: &mut TestAppContext) {
16171    init_test(cx, |_| {});
16172
16173    let editor = cx.add_window(|window, cx| {
16174        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16175        build_editor(buffer, window, cx)
16176    });
16177
16178    _ = editor.update(cx, |editor, window, cx| {
16179        struct Type1;
16180        struct Type2;
16181
16182        let buffer = editor.buffer.read(cx).snapshot(cx);
16183
16184        let anchor_range =
16185            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16186
16187        editor.highlight_background::<Type1>(
16188            &[
16189                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16190                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16191                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16192                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16193            ],
16194            |_| Hsla::red(),
16195            cx,
16196        );
16197        editor.highlight_background::<Type2>(
16198            &[
16199                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16200                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16201                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16202                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16203            ],
16204            |_| Hsla::green(),
16205            cx,
16206        );
16207
16208        let snapshot = editor.snapshot(window, cx);
16209        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16210            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16211            &snapshot,
16212            cx.theme(),
16213        );
16214        assert_eq!(
16215            highlighted_ranges,
16216            &[
16217                (
16218                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16219                    Hsla::green(),
16220                ),
16221                (
16222                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16223                    Hsla::red(),
16224                ),
16225                (
16226                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16227                    Hsla::green(),
16228                ),
16229                (
16230                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16231                    Hsla::red(),
16232                ),
16233            ]
16234        );
16235        assert_eq!(
16236            editor.sorted_background_highlights_in_range(
16237                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16238                &snapshot,
16239                cx.theme(),
16240            ),
16241            &[(
16242                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16243                Hsla::red(),
16244            )]
16245        );
16246    });
16247}
16248
16249#[gpui::test]
16250async fn test_following(cx: &mut TestAppContext) {
16251    init_test(cx, |_| {});
16252
16253    let fs = FakeFs::new(cx.executor());
16254    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16255
16256    let buffer = project.update(cx, |project, cx| {
16257        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16258        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16259    });
16260    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16261    let follower = cx.update(|cx| {
16262        cx.open_window(
16263            WindowOptions {
16264                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16265                    gpui::Point::new(px(0.), px(0.)),
16266                    gpui::Point::new(px(10.), px(80.)),
16267                ))),
16268                ..Default::default()
16269            },
16270            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16271        )
16272        .unwrap()
16273    });
16274
16275    let is_still_following = Rc::new(RefCell::new(true));
16276    let follower_edit_event_count = Rc::new(RefCell::new(0));
16277    let pending_update = Rc::new(RefCell::new(None));
16278    let leader_entity = leader.root(cx).unwrap();
16279    let follower_entity = follower.root(cx).unwrap();
16280    _ = follower.update(cx, {
16281        let update = pending_update.clone();
16282        let is_still_following = is_still_following.clone();
16283        let follower_edit_event_count = follower_edit_event_count.clone();
16284        |_, window, cx| {
16285            cx.subscribe_in(
16286                &leader_entity,
16287                window,
16288                move |_, leader, event, window, cx| {
16289                    leader.read(cx).add_event_to_update_proto(
16290                        event,
16291                        &mut update.borrow_mut(),
16292                        window,
16293                        cx,
16294                    );
16295                },
16296            )
16297            .detach();
16298
16299            cx.subscribe_in(
16300                &follower_entity,
16301                window,
16302                move |_, _, event: &EditorEvent, _window, _cx| {
16303                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16304                        *is_still_following.borrow_mut() = false;
16305                    }
16306
16307                    if let EditorEvent::BufferEdited = event {
16308                        *follower_edit_event_count.borrow_mut() += 1;
16309                    }
16310                },
16311            )
16312            .detach();
16313        }
16314    });
16315
16316    // Update the selections only
16317    _ = leader.update(cx, |leader, window, cx| {
16318        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16319            s.select_ranges([1..1])
16320        });
16321    });
16322    follower
16323        .update(cx, |follower, window, cx| {
16324            follower.apply_update_proto(
16325                &project,
16326                pending_update.borrow_mut().take().unwrap(),
16327                window,
16328                cx,
16329            )
16330        })
16331        .unwrap()
16332        .await
16333        .unwrap();
16334    _ = follower.update(cx, |follower, _, cx| {
16335        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
16336    });
16337    assert!(*is_still_following.borrow());
16338    assert_eq!(*follower_edit_event_count.borrow(), 0);
16339
16340    // Update the scroll position only
16341    _ = leader.update(cx, |leader, window, cx| {
16342        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16343    });
16344    follower
16345        .update(cx, |follower, window, cx| {
16346            follower.apply_update_proto(
16347                &project,
16348                pending_update.borrow_mut().take().unwrap(),
16349                window,
16350                cx,
16351            )
16352        })
16353        .unwrap()
16354        .await
16355        .unwrap();
16356    assert_eq!(
16357        follower
16358            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16359            .unwrap(),
16360        gpui::Point::new(1.5, 3.5)
16361    );
16362    assert!(*is_still_following.borrow());
16363    assert_eq!(*follower_edit_event_count.borrow(), 0);
16364
16365    // Update the selections and scroll position. The follower's scroll position is updated
16366    // via autoscroll, not via the leader's exact scroll position.
16367    _ = leader.update(cx, |leader, window, cx| {
16368        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16369            s.select_ranges([0..0])
16370        });
16371        leader.request_autoscroll(Autoscroll::newest(), cx);
16372        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16373    });
16374    follower
16375        .update(cx, |follower, window, cx| {
16376            follower.apply_update_proto(
16377                &project,
16378                pending_update.borrow_mut().take().unwrap(),
16379                window,
16380                cx,
16381            )
16382        })
16383        .unwrap()
16384        .await
16385        .unwrap();
16386    _ = follower.update(cx, |follower, _, cx| {
16387        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16388        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
16389    });
16390    assert!(*is_still_following.borrow());
16391
16392    // Creating a pending selection that precedes another selection
16393    _ = leader.update(cx, |leader, window, cx| {
16394        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16395            s.select_ranges([1..1])
16396        });
16397        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16398    });
16399    follower
16400        .update(cx, |follower, window, cx| {
16401            follower.apply_update_proto(
16402                &project,
16403                pending_update.borrow_mut().take().unwrap(),
16404                window,
16405                cx,
16406            )
16407        })
16408        .unwrap()
16409        .await
16410        .unwrap();
16411    _ = follower.update(cx, |follower, _, cx| {
16412        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
16413    });
16414    assert!(*is_still_following.borrow());
16415
16416    // Extend the pending selection so that it surrounds another selection
16417    _ = leader.update(cx, |leader, window, cx| {
16418        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16419    });
16420    follower
16421        .update(cx, |follower, window, cx| {
16422            follower.apply_update_proto(
16423                &project,
16424                pending_update.borrow_mut().take().unwrap(),
16425                window,
16426                cx,
16427            )
16428        })
16429        .unwrap()
16430        .await
16431        .unwrap();
16432    _ = follower.update(cx, |follower, _, cx| {
16433        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
16434    });
16435
16436    // Scrolling locally breaks the follow
16437    _ = follower.update(cx, |follower, window, cx| {
16438        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16439        follower.set_scroll_anchor(
16440            ScrollAnchor {
16441                anchor: top_anchor,
16442                offset: gpui::Point::new(0.0, 0.5),
16443            },
16444            window,
16445            cx,
16446        );
16447    });
16448    assert!(!(*is_still_following.borrow()));
16449}
16450
16451#[gpui::test]
16452async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16453    init_test(cx, |_| {});
16454
16455    let fs = FakeFs::new(cx.executor());
16456    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16457    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16458    let pane = workspace
16459        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16460        .unwrap();
16461
16462    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16463
16464    let leader = pane.update_in(cx, |_, window, cx| {
16465        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16466        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16467    });
16468
16469    // Start following the editor when it has no excerpts.
16470    let mut state_message =
16471        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16472    let workspace_entity = workspace.root(cx).unwrap();
16473    let follower_1 = cx
16474        .update_window(*workspace.deref(), |_, window, cx| {
16475            Editor::from_state_proto(
16476                workspace_entity,
16477                ViewId {
16478                    creator: CollaboratorId::PeerId(PeerId::default()),
16479                    id: 0,
16480                },
16481                &mut state_message,
16482                window,
16483                cx,
16484            )
16485        })
16486        .unwrap()
16487        .unwrap()
16488        .await
16489        .unwrap();
16490
16491    let update_message = Rc::new(RefCell::new(None));
16492    follower_1.update_in(cx, {
16493        let update = update_message.clone();
16494        |_, window, cx| {
16495            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16496                leader.read(cx).add_event_to_update_proto(
16497                    event,
16498                    &mut update.borrow_mut(),
16499                    window,
16500                    cx,
16501                );
16502            })
16503            .detach();
16504        }
16505    });
16506
16507    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16508        (
16509            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16510            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16511        )
16512    });
16513
16514    // Insert some excerpts.
16515    leader.update(cx, |leader, cx| {
16516        leader.buffer.update(cx, |multibuffer, cx| {
16517            multibuffer.set_excerpts_for_path(
16518                PathKey::namespaced(1, "b.txt".into()),
16519                buffer_1.clone(),
16520                vec![
16521                    Point::row_range(0..3),
16522                    Point::row_range(1..6),
16523                    Point::row_range(12..15),
16524                ],
16525                0,
16526                cx,
16527            );
16528            multibuffer.set_excerpts_for_path(
16529                PathKey::namespaced(1, "a.txt".into()),
16530                buffer_2.clone(),
16531                vec![Point::row_range(0..6), Point::row_range(8..12)],
16532                0,
16533                cx,
16534            );
16535        });
16536    });
16537
16538    // Apply the update of adding the excerpts.
16539    follower_1
16540        .update_in(cx, |follower, window, cx| {
16541            follower.apply_update_proto(
16542                &project,
16543                update_message.borrow().clone().unwrap(),
16544                window,
16545                cx,
16546            )
16547        })
16548        .await
16549        .unwrap();
16550    assert_eq!(
16551        follower_1.update(cx, |editor, cx| editor.text(cx)),
16552        leader.update(cx, |editor, cx| editor.text(cx))
16553    );
16554    update_message.borrow_mut().take();
16555
16556    // Start following separately after it already has excerpts.
16557    let mut state_message =
16558        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16559    let workspace_entity = workspace.root(cx).unwrap();
16560    let follower_2 = cx
16561        .update_window(*workspace.deref(), |_, window, cx| {
16562            Editor::from_state_proto(
16563                workspace_entity,
16564                ViewId {
16565                    creator: CollaboratorId::PeerId(PeerId::default()),
16566                    id: 0,
16567                },
16568                &mut state_message,
16569                window,
16570                cx,
16571            )
16572        })
16573        .unwrap()
16574        .unwrap()
16575        .await
16576        .unwrap();
16577    assert_eq!(
16578        follower_2.update(cx, |editor, cx| editor.text(cx)),
16579        leader.update(cx, |editor, cx| editor.text(cx))
16580    );
16581
16582    // Remove some excerpts.
16583    leader.update(cx, |leader, cx| {
16584        leader.buffer.update(cx, |multibuffer, cx| {
16585            let excerpt_ids = multibuffer.excerpt_ids();
16586            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16587            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16588        });
16589    });
16590
16591    // Apply the update of removing the excerpts.
16592    follower_1
16593        .update_in(cx, |follower, window, cx| {
16594            follower.apply_update_proto(
16595                &project,
16596                update_message.borrow().clone().unwrap(),
16597                window,
16598                cx,
16599            )
16600        })
16601        .await
16602        .unwrap();
16603    follower_2
16604        .update_in(cx, |follower, window, cx| {
16605            follower.apply_update_proto(
16606                &project,
16607                update_message.borrow().clone().unwrap(),
16608                window,
16609                cx,
16610            )
16611        })
16612        .await
16613        .unwrap();
16614    update_message.borrow_mut().take();
16615    assert_eq!(
16616        follower_1.update(cx, |editor, cx| editor.text(cx)),
16617        leader.update(cx, |editor, cx| editor.text(cx))
16618    );
16619}
16620
16621#[gpui::test]
16622async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16623    init_test(cx, |_| {});
16624
16625    let mut cx = EditorTestContext::new(cx).await;
16626    let lsp_store =
16627        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
16628
16629    cx.set_state(indoc! {"
16630        ˇfn func(abc def: i32) -> u32 {
16631        }
16632    "});
16633
16634    cx.update(|_, cx| {
16635        lsp_store.update(cx, |lsp_store, cx| {
16636            lsp_store
16637                .update_diagnostics(
16638                    LanguageServerId(0),
16639                    lsp::PublishDiagnosticsParams {
16640                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
16641                        version: None,
16642                        diagnostics: vec![
16643                            lsp::Diagnostic {
16644                                range: lsp::Range::new(
16645                                    lsp::Position::new(0, 11),
16646                                    lsp::Position::new(0, 12),
16647                                ),
16648                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16649                                ..Default::default()
16650                            },
16651                            lsp::Diagnostic {
16652                                range: lsp::Range::new(
16653                                    lsp::Position::new(0, 12),
16654                                    lsp::Position::new(0, 15),
16655                                ),
16656                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16657                                ..Default::default()
16658                            },
16659                            lsp::Diagnostic {
16660                                range: lsp::Range::new(
16661                                    lsp::Position::new(0, 25),
16662                                    lsp::Position::new(0, 28),
16663                                ),
16664                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16665                                ..Default::default()
16666                            },
16667                        ],
16668                    },
16669                    None,
16670                    DiagnosticSourceKind::Pushed,
16671                    &[],
16672                    cx,
16673                )
16674                .unwrap()
16675        });
16676    });
16677
16678    executor.run_until_parked();
16679
16680    cx.update_editor(|editor, window, cx| {
16681        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16682    });
16683
16684    cx.assert_editor_state(indoc! {"
16685        fn func(abc def: i32) -> ˇu32 {
16686        }
16687    "});
16688
16689    cx.update_editor(|editor, window, cx| {
16690        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16691    });
16692
16693    cx.assert_editor_state(indoc! {"
16694        fn func(abc ˇdef: i32) -> u32 {
16695        }
16696    "});
16697
16698    cx.update_editor(|editor, window, cx| {
16699        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16700    });
16701
16702    cx.assert_editor_state(indoc! {"
16703        fn func(abcˇ def: i32) -> u32 {
16704        }
16705    "});
16706
16707    cx.update_editor(|editor, window, cx| {
16708        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16709    });
16710
16711    cx.assert_editor_state(indoc! {"
16712        fn func(abc def: i32) -> ˇu32 {
16713        }
16714    "});
16715}
16716
16717#[gpui::test]
16718async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16719    init_test(cx, |_| {});
16720
16721    let mut cx = EditorTestContext::new(cx).await;
16722
16723    let diff_base = r#"
16724        use some::mod;
16725
16726        const A: u32 = 42;
16727
16728        fn main() {
16729            println!("hello");
16730
16731            println!("world");
16732        }
16733        "#
16734    .unindent();
16735
16736    // Edits are modified, removed, modified, added
16737    cx.set_state(
16738        &r#"
16739        use some::modified;
16740
16741        ˇ
16742        fn main() {
16743            println!("hello there");
16744
16745            println!("around the");
16746            println!("world");
16747        }
16748        "#
16749        .unindent(),
16750    );
16751
16752    cx.set_head_text(&diff_base);
16753    executor.run_until_parked();
16754
16755    cx.update_editor(|editor, window, cx| {
16756        //Wrap around the bottom of the buffer
16757        for _ in 0..3 {
16758            editor.go_to_next_hunk(&GoToHunk, window, cx);
16759        }
16760    });
16761
16762    cx.assert_editor_state(
16763        &r#"
16764        ˇuse some::modified;
16765
16766
16767        fn main() {
16768            println!("hello there");
16769
16770            println!("around the");
16771            println!("world");
16772        }
16773        "#
16774        .unindent(),
16775    );
16776
16777    cx.update_editor(|editor, window, cx| {
16778        //Wrap around the top of the buffer
16779        for _ in 0..2 {
16780            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16781        }
16782    });
16783
16784    cx.assert_editor_state(
16785        &r#"
16786        use some::modified;
16787
16788
16789        fn main() {
16790        ˇ    println!("hello there");
16791
16792            println!("around the");
16793            println!("world");
16794        }
16795        "#
16796        .unindent(),
16797    );
16798
16799    cx.update_editor(|editor, window, cx| {
16800        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16801    });
16802
16803    cx.assert_editor_state(
16804        &r#"
16805        use some::modified;
16806
16807        ˇ
16808        fn main() {
16809            println!("hello there");
16810
16811            println!("around the");
16812            println!("world");
16813        }
16814        "#
16815        .unindent(),
16816    );
16817
16818    cx.update_editor(|editor, window, cx| {
16819        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16820    });
16821
16822    cx.assert_editor_state(
16823        &r#"
16824        ˇuse some::modified;
16825
16826
16827        fn main() {
16828            println!("hello there");
16829
16830            println!("around the");
16831            println!("world");
16832        }
16833        "#
16834        .unindent(),
16835    );
16836
16837    cx.update_editor(|editor, window, cx| {
16838        for _ in 0..2 {
16839            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16840        }
16841    });
16842
16843    cx.assert_editor_state(
16844        &r#"
16845        use some::modified;
16846
16847
16848        fn main() {
16849        ˇ    println!("hello there");
16850
16851            println!("around the");
16852            println!("world");
16853        }
16854        "#
16855        .unindent(),
16856    );
16857
16858    cx.update_editor(|editor, window, cx| {
16859        editor.fold(&Fold, window, cx);
16860    });
16861
16862    cx.update_editor(|editor, window, cx| {
16863        editor.go_to_next_hunk(&GoToHunk, window, cx);
16864    });
16865
16866    cx.assert_editor_state(
16867        &r#"
16868        ˇuse some::modified;
16869
16870
16871        fn main() {
16872            println!("hello there");
16873
16874            println!("around the");
16875            println!("world");
16876        }
16877        "#
16878        .unindent(),
16879    );
16880}
16881
16882#[test]
16883fn test_split_words() {
16884    fn split(text: &str) -> Vec<&str> {
16885        split_words(text).collect()
16886    }
16887
16888    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
16889    assert_eq!(split("hello_world"), &["hello_", "world"]);
16890    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
16891    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
16892    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
16893    assert_eq!(split("helloworld"), &["helloworld"]);
16894
16895    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
16896}
16897
16898#[gpui::test]
16899async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
16900    init_test(cx, |_| {});
16901
16902    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
16903    let mut assert = |before, after| {
16904        let _state_context = cx.set_state(before);
16905        cx.run_until_parked();
16906        cx.update_editor(|editor, window, cx| {
16907            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
16908        });
16909        cx.run_until_parked();
16910        cx.assert_editor_state(after);
16911    };
16912
16913    // Outside bracket jumps to outside of matching bracket
16914    assert("console.logˇ(var);", "console.log(var)ˇ;");
16915    assert("console.log(var)ˇ;", "console.logˇ(var);");
16916
16917    // Inside bracket jumps to inside of matching bracket
16918    assert("console.log(ˇvar);", "console.log(varˇ);");
16919    assert("console.log(varˇ);", "console.log(ˇvar);");
16920
16921    // When outside a bracket and inside, favor jumping to the inside bracket
16922    assert(
16923        "console.log('foo', [1, 2, 3]ˇ);",
16924        "console.log(ˇ'foo', [1, 2, 3]);",
16925    );
16926    assert(
16927        "console.log(ˇ'foo', [1, 2, 3]);",
16928        "console.log('foo', [1, 2, 3]ˇ);",
16929    );
16930
16931    // Bias forward if two options are equally likely
16932    assert(
16933        "let result = curried_fun()ˇ();",
16934        "let result = curried_fun()()ˇ;",
16935    );
16936
16937    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
16938    assert(
16939        indoc! {"
16940            function test() {
16941                console.log('test')ˇ
16942            }"},
16943        indoc! {"
16944            function test() {
16945                console.logˇ('test')
16946            }"},
16947    );
16948}
16949
16950#[gpui::test]
16951async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
16952    init_test(cx, |_| {});
16953
16954    let fs = FakeFs::new(cx.executor());
16955    fs.insert_tree(
16956        path!("/a"),
16957        json!({
16958            "main.rs": "fn main() { let a = 5; }",
16959            "other.rs": "// Test file",
16960        }),
16961    )
16962    .await;
16963    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16964
16965    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16966    language_registry.add(Arc::new(Language::new(
16967        LanguageConfig {
16968            name: "Rust".into(),
16969            matcher: LanguageMatcher {
16970                path_suffixes: vec!["rs".to_string()],
16971                ..Default::default()
16972            },
16973            brackets: BracketPairConfig {
16974                pairs: vec![BracketPair {
16975                    start: "{".to_string(),
16976                    end: "}".to_string(),
16977                    close: true,
16978                    surround: true,
16979                    newline: true,
16980                }],
16981                disabled_scopes_by_bracket_ix: Vec::new(),
16982            },
16983            ..Default::default()
16984        },
16985        Some(tree_sitter_rust::LANGUAGE.into()),
16986    )));
16987    let mut fake_servers = language_registry.register_fake_lsp(
16988        "Rust",
16989        FakeLspAdapter {
16990            capabilities: lsp::ServerCapabilities {
16991                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
16992                    first_trigger_character: "{".to_string(),
16993                    more_trigger_character: None,
16994                }),
16995                ..Default::default()
16996            },
16997            ..Default::default()
16998        },
16999    );
17000
17001    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17002
17003    let cx = &mut VisualTestContext::from_window(*workspace, cx);
17004
17005    let worktree_id = workspace
17006        .update(cx, |workspace, _, cx| {
17007            workspace.project().update(cx, |project, cx| {
17008                project.worktrees(cx).next().unwrap().read(cx).id()
17009            })
17010        })
17011        .unwrap();
17012
17013    let buffer = project
17014        .update(cx, |project, cx| {
17015            project.open_local_buffer(path!("/a/main.rs"), cx)
17016        })
17017        .await
17018        .unwrap();
17019    let editor_handle = workspace
17020        .update(cx, |workspace, window, cx| {
17021            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
17022        })
17023        .unwrap()
17024        .await
17025        .unwrap()
17026        .downcast::<Editor>()
17027        .unwrap();
17028
17029    cx.executor().start_waiting();
17030    let fake_server = fake_servers.next().await.unwrap();
17031
17032    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
17033        |params, _| async move {
17034            assert_eq!(
17035                params.text_document_position.text_document.uri,
17036                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
17037            );
17038            assert_eq!(
17039                params.text_document_position.position,
17040                lsp::Position::new(0, 21),
17041            );
17042
17043            Ok(Some(vec![lsp::TextEdit {
17044                new_text: "]".to_string(),
17045                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17046            }]))
17047        },
17048    );
17049
17050    editor_handle.update_in(cx, |editor, window, cx| {
17051        window.focus(&editor.focus_handle(cx));
17052        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17053            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
17054        });
17055        editor.handle_input("{", window, cx);
17056    });
17057
17058    cx.executor().run_until_parked();
17059
17060    buffer.update(cx, |buffer, _| {
17061        assert_eq!(
17062            buffer.text(),
17063            "fn main() { let a = {5}; }",
17064            "No extra braces from on type formatting should appear in the buffer"
17065        )
17066    });
17067}
17068
17069#[gpui::test(iterations = 20, seeds(31))]
17070async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
17071    init_test(cx, |_| {});
17072
17073    let mut cx = EditorLspTestContext::new_rust(
17074        lsp::ServerCapabilities {
17075            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17076                first_trigger_character: ".".to_string(),
17077                more_trigger_character: None,
17078            }),
17079            ..Default::default()
17080        },
17081        cx,
17082    )
17083    .await;
17084
17085    cx.update_buffer(|buffer, _| {
17086        // This causes autoindent to be async.
17087        buffer.set_sync_parse_timeout(Duration::ZERO)
17088    });
17089
17090    cx.set_state("fn c() {\n    d()ˇ\n}\n");
17091    cx.simulate_keystroke("\n");
17092    cx.run_until_parked();
17093
17094    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
17095    let mut request =
17096        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
17097            let buffer_cloned = buffer_cloned.clone();
17098            async move {
17099                buffer_cloned.update(&mut cx, |buffer, _| {
17100                    assert_eq!(
17101                        buffer.text(),
17102                        "fn c() {\n    d()\n        .\n}\n",
17103                        "OnTypeFormatting should triggered after autoindent applied"
17104                    )
17105                })?;
17106
17107                Ok(Some(vec![]))
17108            }
17109        });
17110
17111    cx.simulate_keystroke(".");
17112    cx.run_until_parked();
17113
17114    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
17115    assert!(request.next().await.is_some());
17116    request.close();
17117    assert!(request.next().await.is_none());
17118}
17119
17120#[gpui::test]
17121async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17122    init_test(cx, |_| {});
17123
17124    let fs = FakeFs::new(cx.executor());
17125    fs.insert_tree(
17126        path!("/a"),
17127        json!({
17128            "main.rs": "fn main() { let a = 5; }",
17129            "other.rs": "// Test file",
17130        }),
17131    )
17132    .await;
17133
17134    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17135
17136    let server_restarts = Arc::new(AtomicUsize::new(0));
17137    let closure_restarts = Arc::clone(&server_restarts);
17138    let language_server_name = "test language server";
17139    let language_name: LanguageName = "Rust".into();
17140
17141    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17142    language_registry.add(Arc::new(Language::new(
17143        LanguageConfig {
17144            name: language_name.clone(),
17145            matcher: LanguageMatcher {
17146                path_suffixes: vec!["rs".to_string()],
17147                ..Default::default()
17148            },
17149            ..Default::default()
17150        },
17151        Some(tree_sitter_rust::LANGUAGE.into()),
17152    )));
17153    let mut fake_servers = language_registry.register_fake_lsp(
17154        "Rust",
17155        FakeLspAdapter {
17156            name: language_server_name,
17157            initialization_options: Some(json!({
17158                "testOptionValue": true
17159            })),
17160            initializer: Some(Box::new(move |fake_server| {
17161                let task_restarts = Arc::clone(&closure_restarts);
17162                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17163                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17164                    futures::future::ready(Ok(()))
17165                });
17166            })),
17167            ..Default::default()
17168        },
17169    );
17170
17171    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17172    let _buffer = project
17173        .update(cx, |project, cx| {
17174            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17175        })
17176        .await
17177        .unwrap();
17178    let _fake_server = fake_servers.next().await.unwrap();
17179    update_test_language_settings(cx, |language_settings| {
17180        language_settings.languages.0.insert(
17181            language_name.clone().0,
17182            LanguageSettingsContent {
17183                tab_size: NonZeroU32::new(8),
17184                ..Default::default()
17185            },
17186        );
17187    });
17188    cx.executor().run_until_parked();
17189    assert_eq!(
17190        server_restarts.load(atomic::Ordering::Acquire),
17191        0,
17192        "Should not restart LSP server on an unrelated change"
17193    );
17194
17195    update_test_project_settings(cx, |project_settings| {
17196        project_settings.lsp.insert(
17197            "Some other server name".into(),
17198            LspSettings {
17199                binary: None,
17200                settings: None,
17201                initialization_options: Some(json!({
17202                    "some other init value": false
17203                })),
17204                enable_lsp_tasks: false,
17205                fetch: None,
17206            },
17207        );
17208    });
17209    cx.executor().run_until_parked();
17210    assert_eq!(
17211        server_restarts.load(atomic::Ordering::Acquire),
17212        0,
17213        "Should not restart LSP server on an unrelated LSP settings change"
17214    );
17215
17216    update_test_project_settings(cx, |project_settings| {
17217        project_settings.lsp.insert(
17218            language_server_name.into(),
17219            LspSettings {
17220                binary: None,
17221                settings: None,
17222                initialization_options: Some(json!({
17223                    "anotherInitValue": false
17224                })),
17225                enable_lsp_tasks: false,
17226                fetch: None,
17227            },
17228        );
17229    });
17230    cx.executor().run_until_parked();
17231    assert_eq!(
17232        server_restarts.load(atomic::Ordering::Acquire),
17233        1,
17234        "Should restart LSP server on a related LSP settings change"
17235    );
17236
17237    update_test_project_settings(cx, |project_settings| {
17238        project_settings.lsp.insert(
17239            language_server_name.into(),
17240            LspSettings {
17241                binary: None,
17242                settings: None,
17243                initialization_options: Some(json!({
17244                    "anotherInitValue": false
17245                })),
17246                enable_lsp_tasks: false,
17247                fetch: None,
17248            },
17249        );
17250    });
17251    cx.executor().run_until_parked();
17252    assert_eq!(
17253        server_restarts.load(atomic::Ordering::Acquire),
17254        1,
17255        "Should not restart LSP server on a related LSP settings change that is the same"
17256    );
17257
17258    update_test_project_settings(cx, |project_settings| {
17259        project_settings.lsp.insert(
17260            language_server_name.into(),
17261            LspSettings {
17262                binary: None,
17263                settings: None,
17264                initialization_options: None,
17265                enable_lsp_tasks: false,
17266                fetch: None,
17267            },
17268        );
17269    });
17270    cx.executor().run_until_parked();
17271    assert_eq!(
17272        server_restarts.load(atomic::Ordering::Acquire),
17273        2,
17274        "Should restart LSP server on another related LSP settings change"
17275    );
17276}
17277
17278#[gpui::test]
17279async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17280    init_test(cx, |_| {});
17281
17282    let mut cx = EditorLspTestContext::new_rust(
17283        lsp::ServerCapabilities {
17284            completion_provider: Some(lsp::CompletionOptions {
17285                trigger_characters: Some(vec![".".to_string()]),
17286                resolve_provider: Some(true),
17287                ..Default::default()
17288            }),
17289            ..Default::default()
17290        },
17291        cx,
17292    )
17293    .await;
17294
17295    cx.set_state("fn main() { let a = 2ˇ; }");
17296    cx.simulate_keystroke(".");
17297    let completion_item = lsp::CompletionItem {
17298        label: "some".into(),
17299        kind: Some(lsp::CompletionItemKind::SNIPPET),
17300        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17301        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17302            kind: lsp::MarkupKind::Markdown,
17303            value: "```rust\nSome(2)\n```".to_string(),
17304        })),
17305        deprecated: Some(false),
17306        sort_text: Some("fffffff2".to_string()),
17307        filter_text: Some("some".to_string()),
17308        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17309        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17310            range: lsp::Range {
17311                start: lsp::Position {
17312                    line: 0,
17313                    character: 22,
17314                },
17315                end: lsp::Position {
17316                    line: 0,
17317                    character: 22,
17318                },
17319            },
17320            new_text: "Some(2)".to_string(),
17321        })),
17322        additional_text_edits: Some(vec![lsp::TextEdit {
17323            range: lsp::Range {
17324                start: lsp::Position {
17325                    line: 0,
17326                    character: 20,
17327                },
17328                end: lsp::Position {
17329                    line: 0,
17330                    character: 22,
17331                },
17332            },
17333            new_text: "".to_string(),
17334        }]),
17335        ..Default::default()
17336    };
17337
17338    let closure_completion_item = completion_item.clone();
17339    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17340        let task_completion_item = closure_completion_item.clone();
17341        async move {
17342            Ok(Some(lsp::CompletionResponse::Array(vec![
17343                task_completion_item,
17344            ])))
17345        }
17346    });
17347
17348    request.next().await;
17349
17350    cx.condition(|editor, _| editor.context_menu_visible())
17351        .await;
17352    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17353        editor
17354            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17355            .unwrap()
17356    });
17357    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17358
17359    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17360        let task_completion_item = completion_item.clone();
17361        async move { Ok(task_completion_item) }
17362    })
17363    .next()
17364    .await
17365    .unwrap();
17366    apply_additional_edits.await.unwrap();
17367    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17368}
17369
17370#[gpui::test]
17371async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17372    init_test(cx, |_| {});
17373
17374    let mut cx = EditorLspTestContext::new_rust(
17375        lsp::ServerCapabilities {
17376            completion_provider: Some(lsp::CompletionOptions {
17377                trigger_characters: Some(vec![".".to_string()]),
17378                resolve_provider: Some(true),
17379                ..Default::default()
17380            }),
17381            ..Default::default()
17382        },
17383        cx,
17384    )
17385    .await;
17386
17387    cx.set_state("fn main() { let a = 2ˇ; }");
17388    cx.simulate_keystroke(".");
17389
17390    let item1 = lsp::CompletionItem {
17391        label: "method id()".to_string(),
17392        filter_text: Some("id".to_string()),
17393        detail: None,
17394        documentation: None,
17395        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17396            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17397            new_text: ".id".to_string(),
17398        })),
17399        ..lsp::CompletionItem::default()
17400    };
17401
17402    let item2 = lsp::CompletionItem {
17403        label: "other".to_string(),
17404        filter_text: Some("other".to_string()),
17405        detail: None,
17406        documentation: None,
17407        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17408            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17409            new_text: ".other".to_string(),
17410        })),
17411        ..lsp::CompletionItem::default()
17412    };
17413
17414    let item1 = item1.clone();
17415    cx.set_request_handler::<lsp::request::Completion, _, _>({
17416        let item1 = item1.clone();
17417        move |_, _, _| {
17418            let item1 = item1.clone();
17419            let item2 = item2.clone();
17420            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17421        }
17422    })
17423    .next()
17424    .await;
17425
17426    cx.condition(|editor, _| editor.context_menu_visible())
17427        .await;
17428    cx.update_editor(|editor, _, _| {
17429        let context_menu = editor.context_menu.borrow_mut();
17430        let context_menu = context_menu
17431            .as_ref()
17432            .expect("Should have the context menu deployed");
17433        match context_menu {
17434            CodeContextMenu::Completions(completions_menu) => {
17435                let completions = completions_menu.completions.borrow_mut();
17436                assert_eq!(
17437                    completions
17438                        .iter()
17439                        .map(|completion| &completion.label.text)
17440                        .collect::<Vec<_>>(),
17441                    vec!["method id()", "other"]
17442                )
17443            }
17444            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17445        }
17446    });
17447
17448    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17449        let item1 = item1.clone();
17450        move |_, item_to_resolve, _| {
17451            let item1 = item1.clone();
17452            async move {
17453                if item1 == item_to_resolve {
17454                    Ok(lsp::CompletionItem {
17455                        label: "method id()".to_string(),
17456                        filter_text: Some("id".to_string()),
17457                        detail: Some("Now resolved!".to_string()),
17458                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17459                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17460                            range: lsp::Range::new(
17461                                lsp::Position::new(0, 22),
17462                                lsp::Position::new(0, 22),
17463                            ),
17464                            new_text: ".id".to_string(),
17465                        })),
17466                        ..lsp::CompletionItem::default()
17467                    })
17468                } else {
17469                    Ok(item_to_resolve)
17470                }
17471            }
17472        }
17473    })
17474    .next()
17475    .await
17476    .unwrap();
17477    cx.run_until_parked();
17478
17479    cx.update_editor(|editor, window, cx| {
17480        editor.context_menu_next(&Default::default(), window, cx);
17481    });
17482
17483    cx.update_editor(|editor, _, _| {
17484        let context_menu = editor.context_menu.borrow_mut();
17485        let context_menu = context_menu
17486            .as_ref()
17487            .expect("Should have the context menu deployed");
17488        match context_menu {
17489            CodeContextMenu::Completions(completions_menu) => {
17490                let completions = completions_menu.completions.borrow_mut();
17491                assert_eq!(
17492                    completions
17493                        .iter()
17494                        .map(|completion| &completion.label.text)
17495                        .collect::<Vec<_>>(),
17496                    vec!["method id() Now resolved!", "other"],
17497                    "Should update first completion label, but not second as the filter text did not match."
17498                );
17499            }
17500            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17501        }
17502    });
17503}
17504
17505#[gpui::test]
17506async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17507    init_test(cx, |_| {});
17508    let mut cx = EditorLspTestContext::new_rust(
17509        lsp::ServerCapabilities {
17510            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17511            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17512            completion_provider: Some(lsp::CompletionOptions {
17513                resolve_provider: Some(true),
17514                ..Default::default()
17515            }),
17516            ..Default::default()
17517        },
17518        cx,
17519    )
17520    .await;
17521    cx.set_state(indoc! {"
17522        struct TestStruct {
17523            field: i32
17524        }
17525
17526        fn mainˇ() {
17527            let unused_var = 42;
17528            let test_struct = TestStruct { field: 42 };
17529        }
17530    "});
17531    let symbol_range = cx.lsp_range(indoc! {"
17532        struct TestStruct {
17533            field: i32
17534        }
17535
17536        «fn main»() {
17537            let unused_var = 42;
17538            let test_struct = TestStruct { field: 42 };
17539        }
17540    "});
17541    let mut hover_requests =
17542        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17543            Ok(Some(lsp::Hover {
17544                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17545                    kind: lsp::MarkupKind::Markdown,
17546                    value: "Function documentation".to_string(),
17547                }),
17548                range: Some(symbol_range),
17549            }))
17550        });
17551
17552    // Case 1: Test that code action menu hide hover popover
17553    cx.dispatch_action(Hover);
17554    hover_requests.next().await;
17555    cx.condition(|editor, _| editor.hover_state.visible()).await;
17556    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17557        move |_, _, _| async move {
17558            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17559                lsp::CodeAction {
17560                    title: "Remove unused variable".to_string(),
17561                    kind: Some(CodeActionKind::QUICKFIX),
17562                    edit: Some(lsp::WorkspaceEdit {
17563                        changes: Some(
17564                            [(
17565                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17566                                vec![lsp::TextEdit {
17567                                    range: lsp::Range::new(
17568                                        lsp::Position::new(5, 4),
17569                                        lsp::Position::new(5, 27),
17570                                    ),
17571                                    new_text: "".to_string(),
17572                                }],
17573                            )]
17574                            .into_iter()
17575                            .collect(),
17576                        ),
17577                        ..Default::default()
17578                    }),
17579                    ..Default::default()
17580                },
17581            )]))
17582        },
17583    );
17584    cx.update_editor(|editor, window, cx| {
17585        editor.toggle_code_actions(
17586            &ToggleCodeActions {
17587                deployed_from: None,
17588                quick_launch: false,
17589            },
17590            window,
17591            cx,
17592        );
17593    });
17594    code_action_requests.next().await;
17595    cx.run_until_parked();
17596    cx.condition(|editor, _| editor.context_menu_visible())
17597        .await;
17598    cx.update_editor(|editor, _, _| {
17599        assert!(
17600            !editor.hover_state.visible(),
17601            "Hover popover should be hidden when code action menu is shown"
17602        );
17603        // Hide code actions
17604        editor.context_menu.take();
17605    });
17606
17607    // Case 2: Test that code completions hide hover popover
17608    cx.dispatch_action(Hover);
17609    hover_requests.next().await;
17610    cx.condition(|editor, _| editor.hover_state.visible()).await;
17611    let counter = Arc::new(AtomicUsize::new(0));
17612    let mut completion_requests =
17613        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17614            let counter = counter.clone();
17615            async move {
17616                counter.fetch_add(1, atomic::Ordering::Release);
17617                Ok(Some(lsp::CompletionResponse::Array(vec![
17618                    lsp::CompletionItem {
17619                        label: "main".into(),
17620                        kind: Some(lsp::CompletionItemKind::FUNCTION),
17621                        detail: Some("() -> ()".to_string()),
17622                        ..Default::default()
17623                    },
17624                    lsp::CompletionItem {
17625                        label: "TestStruct".into(),
17626                        kind: Some(lsp::CompletionItemKind::STRUCT),
17627                        detail: Some("struct TestStruct".to_string()),
17628                        ..Default::default()
17629                    },
17630                ])))
17631            }
17632        });
17633    cx.update_editor(|editor, window, cx| {
17634        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
17635    });
17636    completion_requests.next().await;
17637    cx.condition(|editor, _| editor.context_menu_visible())
17638        .await;
17639    cx.update_editor(|editor, _, _| {
17640        assert!(
17641            !editor.hover_state.visible(),
17642            "Hover popover should be hidden when completion menu is shown"
17643        );
17644    });
17645}
17646
17647#[gpui::test]
17648async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
17649    init_test(cx, |_| {});
17650
17651    let mut cx = EditorLspTestContext::new_rust(
17652        lsp::ServerCapabilities {
17653            completion_provider: Some(lsp::CompletionOptions {
17654                trigger_characters: Some(vec![".".to_string()]),
17655                resolve_provider: Some(true),
17656                ..Default::default()
17657            }),
17658            ..Default::default()
17659        },
17660        cx,
17661    )
17662    .await;
17663
17664    cx.set_state("fn main() { let a = 2ˇ; }");
17665    cx.simulate_keystroke(".");
17666
17667    let unresolved_item_1 = lsp::CompletionItem {
17668        label: "id".to_string(),
17669        filter_text: Some("id".to_string()),
17670        detail: None,
17671        documentation: None,
17672        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17673            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17674            new_text: ".id".to_string(),
17675        })),
17676        ..lsp::CompletionItem::default()
17677    };
17678    let resolved_item_1 = lsp::CompletionItem {
17679        additional_text_edits: Some(vec![lsp::TextEdit {
17680            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17681            new_text: "!!".to_string(),
17682        }]),
17683        ..unresolved_item_1.clone()
17684    };
17685    let unresolved_item_2 = lsp::CompletionItem {
17686        label: "other".to_string(),
17687        filter_text: Some("other".to_string()),
17688        detail: None,
17689        documentation: None,
17690        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17691            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17692            new_text: ".other".to_string(),
17693        })),
17694        ..lsp::CompletionItem::default()
17695    };
17696    let resolved_item_2 = lsp::CompletionItem {
17697        additional_text_edits: Some(vec![lsp::TextEdit {
17698            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17699            new_text: "??".to_string(),
17700        }]),
17701        ..unresolved_item_2.clone()
17702    };
17703
17704    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
17705    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
17706    cx.lsp
17707        .server
17708        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17709            let unresolved_item_1 = unresolved_item_1.clone();
17710            let resolved_item_1 = resolved_item_1.clone();
17711            let unresolved_item_2 = unresolved_item_2.clone();
17712            let resolved_item_2 = resolved_item_2.clone();
17713            let resolve_requests_1 = resolve_requests_1.clone();
17714            let resolve_requests_2 = resolve_requests_2.clone();
17715            move |unresolved_request, _| {
17716                let unresolved_item_1 = unresolved_item_1.clone();
17717                let resolved_item_1 = resolved_item_1.clone();
17718                let unresolved_item_2 = unresolved_item_2.clone();
17719                let resolved_item_2 = resolved_item_2.clone();
17720                let resolve_requests_1 = resolve_requests_1.clone();
17721                let resolve_requests_2 = resolve_requests_2.clone();
17722                async move {
17723                    if unresolved_request == unresolved_item_1 {
17724                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
17725                        Ok(resolved_item_1.clone())
17726                    } else if unresolved_request == unresolved_item_2 {
17727                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
17728                        Ok(resolved_item_2.clone())
17729                    } else {
17730                        panic!("Unexpected completion item {unresolved_request:?}")
17731                    }
17732                }
17733            }
17734        })
17735        .detach();
17736
17737    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17738        let unresolved_item_1 = unresolved_item_1.clone();
17739        let unresolved_item_2 = unresolved_item_2.clone();
17740        async move {
17741            Ok(Some(lsp::CompletionResponse::Array(vec![
17742                unresolved_item_1,
17743                unresolved_item_2,
17744            ])))
17745        }
17746    })
17747    .next()
17748    .await;
17749
17750    cx.condition(|editor, _| editor.context_menu_visible())
17751        .await;
17752    cx.update_editor(|editor, _, _| {
17753        let context_menu = editor.context_menu.borrow_mut();
17754        let context_menu = context_menu
17755            .as_ref()
17756            .expect("Should have the context menu deployed");
17757        match context_menu {
17758            CodeContextMenu::Completions(completions_menu) => {
17759                let completions = completions_menu.completions.borrow_mut();
17760                assert_eq!(
17761                    completions
17762                        .iter()
17763                        .map(|completion| &completion.label.text)
17764                        .collect::<Vec<_>>(),
17765                    vec!["id", "other"]
17766                )
17767            }
17768            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17769        }
17770    });
17771    cx.run_until_parked();
17772
17773    cx.update_editor(|editor, window, cx| {
17774        editor.context_menu_next(&ContextMenuNext, window, cx);
17775    });
17776    cx.run_until_parked();
17777    cx.update_editor(|editor, window, cx| {
17778        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17779    });
17780    cx.run_until_parked();
17781    cx.update_editor(|editor, window, cx| {
17782        editor.context_menu_next(&ContextMenuNext, window, cx);
17783    });
17784    cx.run_until_parked();
17785    cx.update_editor(|editor, window, cx| {
17786        editor
17787            .compose_completion(&ComposeCompletion::default(), window, cx)
17788            .expect("No task returned")
17789    })
17790    .await
17791    .expect("Completion failed");
17792    cx.run_until_parked();
17793
17794    cx.update_editor(|editor, _, cx| {
17795        assert_eq!(
17796            resolve_requests_1.load(atomic::Ordering::Acquire),
17797            1,
17798            "Should always resolve once despite multiple selections"
17799        );
17800        assert_eq!(
17801            resolve_requests_2.load(atomic::Ordering::Acquire),
17802            1,
17803            "Should always resolve once after multiple selections and applying the completion"
17804        );
17805        assert_eq!(
17806            editor.text(cx),
17807            "fn main() { let a = ??.other; }",
17808            "Should use resolved data when applying the completion"
17809        );
17810    });
17811}
17812
17813#[gpui::test]
17814async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
17815    init_test(cx, |_| {});
17816
17817    let item_0 = lsp::CompletionItem {
17818        label: "abs".into(),
17819        insert_text: Some("abs".into()),
17820        data: Some(json!({ "very": "special"})),
17821        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
17822        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
17823            lsp::InsertReplaceEdit {
17824                new_text: "abs".to_string(),
17825                insert: lsp::Range::default(),
17826                replace: lsp::Range::default(),
17827            },
17828        )),
17829        ..lsp::CompletionItem::default()
17830    };
17831    let items = iter::once(item_0.clone())
17832        .chain((11..51).map(|i| lsp::CompletionItem {
17833            label: format!("item_{}", i),
17834            insert_text: Some(format!("item_{}", i)),
17835            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
17836            ..lsp::CompletionItem::default()
17837        }))
17838        .collect::<Vec<_>>();
17839
17840    let default_commit_characters = vec!["?".to_string()];
17841    let default_data = json!({ "default": "data"});
17842    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
17843    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
17844    let default_edit_range = lsp::Range {
17845        start: lsp::Position {
17846            line: 0,
17847            character: 5,
17848        },
17849        end: lsp::Position {
17850            line: 0,
17851            character: 5,
17852        },
17853    };
17854
17855    let mut cx = EditorLspTestContext::new_rust(
17856        lsp::ServerCapabilities {
17857            completion_provider: Some(lsp::CompletionOptions {
17858                trigger_characters: Some(vec![".".to_string()]),
17859                resolve_provider: Some(true),
17860                ..Default::default()
17861            }),
17862            ..Default::default()
17863        },
17864        cx,
17865    )
17866    .await;
17867
17868    cx.set_state("fn main() { let a = 2ˇ; }");
17869    cx.simulate_keystroke(".");
17870
17871    let completion_data = default_data.clone();
17872    let completion_characters = default_commit_characters.clone();
17873    let completion_items = items.clone();
17874    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17875        let default_data = completion_data.clone();
17876        let default_commit_characters = completion_characters.clone();
17877        let items = completion_items.clone();
17878        async move {
17879            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
17880                items,
17881                item_defaults: Some(lsp::CompletionListItemDefaults {
17882                    data: Some(default_data.clone()),
17883                    commit_characters: Some(default_commit_characters.clone()),
17884                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
17885                        default_edit_range,
17886                    )),
17887                    insert_text_format: Some(default_insert_text_format),
17888                    insert_text_mode: Some(default_insert_text_mode),
17889                }),
17890                ..lsp::CompletionList::default()
17891            })))
17892        }
17893    })
17894    .next()
17895    .await;
17896
17897    let resolved_items = Arc::new(Mutex::new(Vec::new()));
17898    cx.lsp
17899        .server
17900        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17901            let closure_resolved_items = resolved_items.clone();
17902            move |item_to_resolve, _| {
17903                let closure_resolved_items = closure_resolved_items.clone();
17904                async move {
17905                    closure_resolved_items.lock().push(item_to_resolve.clone());
17906                    Ok(item_to_resolve)
17907                }
17908            }
17909        })
17910        .detach();
17911
17912    cx.condition(|editor, _| editor.context_menu_visible())
17913        .await;
17914    cx.run_until_parked();
17915    cx.update_editor(|editor, _, _| {
17916        let menu = editor.context_menu.borrow_mut();
17917        match menu.as_ref().expect("should have the completions menu") {
17918            CodeContextMenu::Completions(completions_menu) => {
17919                assert_eq!(
17920                    completions_menu
17921                        .entries
17922                        .borrow()
17923                        .iter()
17924                        .map(|mat| mat.string.clone())
17925                        .collect::<Vec<String>>(),
17926                    items
17927                        .iter()
17928                        .map(|completion| completion.label.clone())
17929                        .collect::<Vec<String>>()
17930                );
17931            }
17932            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
17933        }
17934    });
17935    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
17936    // with 4 from the end.
17937    assert_eq!(
17938        *resolved_items.lock(),
17939        [&items[0..16], &items[items.len() - 4..items.len()]]
17940            .concat()
17941            .iter()
17942            .cloned()
17943            .map(|mut item| {
17944                if item.data.is_none() {
17945                    item.data = Some(default_data.clone());
17946                }
17947                item
17948            })
17949            .collect::<Vec<lsp::CompletionItem>>(),
17950        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
17951    );
17952    resolved_items.lock().clear();
17953
17954    cx.update_editor(|editor, window, cx| {
17955        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17956    });
17957    cx.run_until_parked();
17958    // Completions that have already been resolved are skipped.
17959    assert_eq!(
17960        *resolved_items.lock(),
17961        items[items.len() - 17..items.len() - 4]
17962            .iter()
17963            .cloned()
17964            .map(|mut item| {
17965                if item.data.is_none() {
17966                    item.data = Some(default_data.clone());
17967                }
17968                item
17969            })
17970            .collect::<Vec<lsp::CompletionItem>>()
17971    );
17972    resolved_items.lock().clear();
17973}
17974
17975#[gpui::test]
17976async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
17977    init_test(cx, |_| {});
17978
17979    let mut cx = EditorLspTestContext::new(
17980        Language::new(
17981            LanguageConfig {
17982                matcher: LanguageMatcher {
17983                    path_suffixes: vec!["jsx".into()],
17984                    ..Default::default()
17985                },
17986                overrides: [(
17987                    "element".into(),
17988                    LanguageConfigOverride {
17989                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
17990                        ..Default::default()
17991                    },
17992                )]
17993                .into_iter()
17994                .collect(),
17995                ..Default::default()
17996            },
17997            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
17998        )
17999        .with_override_query("(jsx_self_closing_element) @element")
18000        .unwrap(),
18001        lsp::ServerCapabilities {
18002            completion_provider: Some(lsp::CompletionOptions {
18003                trigger_characters: Some(vec![":".to_string()]),
18004                ..Default::default()
18005            }),
18006            ..Default::default()
18007        },
18008        cx,
18009    )
18010    .await;
18011
18012    cx.lsp
18013        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
18014            Ok(Some(lsp::CompletionResponse::Array(vec![
18015                lsp::CompletionItem {
18016                    label: "bg-blue".into(),
18017                    ..Default::default()
18018                },
18019                lsp::CompletionItem {
18020                    label: "bg-red".into(),
18021                    ..Default::default()
18022                },
18023                lsp::CompletionItem {
18024                    label: "bg-yellow".into(),
18025                    ..Default::default()
18026                },
18027            ])))
18028        });
18029
18030    cx.set_state(r#"<p class="bgˇ" />"#);
18031
18032    // Trigger completion when typing a dash, because the dash is an extra
18033    // word character in the 'element' scope, which contains the cursor.
18034    cx.simulate_keystroke("-");
18035    cx.executor().run_until_parked();
18036    cx.update_editor(|editor, _, _| {
18037        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18038        {
18039            assert_eq!(
18040                completion_menu_entries(menu),
18041                &["bg-blue", "bg-red", "bg-yellow"]
18042            );
18043        } else {
18044            panic!("expected completion menu to be open");
18045        }
18046    });
18047
18048    cx.simulate_keystroke("l");
18049    cx.executor().run_until_parked();
18050    cx.update_editor(|editor, _, _| {
18051        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18052        {
18053            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
18054        } else {
18055            panic!("expected completion menu to be open");
18056        }
18057    });
18058
18059    // When filtering completions, consider the character after the '-' to
18060    // be the start of a subword.
18061    cx.set_state(r#"<p class="yelˇ" />"#);
18062    cx.simulate_keystroke("l");
18063    cx.executor().run_until_parked();
18064    cx.update_editor(|editor, _, _| {
18065        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18066        {
18067            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
18068        } else {
18069            panic!("expected completion menu to be open");
18070        }
18071    });
18072}
18073
18074fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
18075    let entries = menu.entries.borrow();
18076    entries.iter().map(|mat| mat.string.clone()).collect()
18077}
18078
18079#[gpui::test]
18080async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
18081    init_test(cx, |settings| {
18082        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
18083            Formatter::Prettier,
18084        )))
18085    });
18086
18087    let fs = FakeFs::new(cx.executor());
18088    fs.insert_file(path!("/file.ts"), Default::default()).await;
18089
18090    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
18091    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18092
18093    language_registry.add(Arc::new(Language::new(
18094        LanguageConfig {
18095            name: "TypeScript".into(),
18096            matcher: LanguageMatcher {
18097                path_suffixes: vec!["ts".to_string()],
18098                ..Default::default()
18099            },
18100            ..Default::default()
18101        },
18102        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18103    )));
18104    update_test_language_settings(cx, |settings| {
18105        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
18106    });
18107
18108    let test_plugin = "test_plugin";
18109    let _ = language_registry.register_fake_lsp(
18110        "TypeScript",
18111        FakeLspAdapter {
18112            prettier_plugins: vec![test_plugin],
18113            ..Default::default()
18114        },
18115    );
18116
18117    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
18118    let buffer = project
18119        .update(cx, |project, cx| {
18120            project.open_local_buffer(path!("/file.ts"), cx)
18121        })
18122        .await
18123        .unwrap();
18124
18125    let buffer_text = "one\ntwo\nthree\n";
18126    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18127    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18128    editor.update_in(cx, |editor, window, cx| {
18129        editor.set_text(buffer_text, window, cx)
18130    });
18131
18132    editor
18133        .update_in(cx, |editor, window, cx| {
18134            editor.perform_format(
18135                project.clone(),
18136                FormatTrigger::Manual,
18137                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18138                window,
18139                cx,
18140            )
18141        })
18142        .unwrap()
18143        .await;
18144    assert_eq!(
18145        editor.update(cx, |editor, cx| editor.text(cx)),
18146        buffer_text.to_string() + prettier_format_suffix,
18147        "Test prettier formatting was not applied to the original buffer text",
18148    );
18149
18150    update_test_language_settings(cx, |settings| {
18151        settings.defaults.formatter = Some(SelectedFormatter::Auto)
18152    });
18153    let format = editor.update_in(cx, |editor, window, cx| {
18154        editor.perform_format(
18155            project.clone(),
18156            FormatTrigger::Manual,
18157            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18158            window,
18159            cx,
18160        )
18161    });
18162    format.await.unwrap();
18163    assert_eq!(
18164        editor.update(cx, |editor, cx| editor.text(cx)),
18165        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18166        "Autoformatting (via test prettier) was not applied to the original buffer text",
18167    );
18168}
18169
18170#[gpui::test]
18171async fn test_addition_reverts(cx: &mut TestAppContext) {
18172    init_test(cx, |_| {});
18173    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18174    let base_text = indoc! {r#"
18175        struct Row;
18176        struct Row1;
18177        struct Row2;
18178
18179        struct Row4;
18180        struct Row5;
18181        struct Row6;
18182
18183        struct Row8;
18184        struct Row9;
18185        struct Row10;"#};
18186
18187    // When addition hunks are not adjacent to carets, no hunk revert is performed
18188    assert_hunk_revert(
18189        indoc! {r#"struct Row;
18190                   struct Row1;
18191                   struct Row1.1;
18192                   struct Row1.2;
18193                   struct Row2;ˇ
18194
18195                   struct Row4;
18196                   struct Row5;
18197                   struct Row6;
18198
18199                   struct Row8;
18200                   ˇstruct Row9;
18201                   struct Row9.1;
18202                   struct Row9.2;
18203                   struct Row9.3;
18204                   struct Row10;"#},
18205        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18206        indoc! {r#"struct Row;
18207                   struct Row1;
18208                   struct Row1.1;
18209                   struct Row1.2;
18210                   struct Row2;ˇ
18211
18212                   struct Row4;
18213                   struct Row5;
18214                   struct Row6;
18215
18216                   struct Row8;
18217                   ˇstruct Row9;
18218                   struct Row9.1;
18219                   struct Row9.2;
18220                   struct Row9.3;
18221                   struct Row10;"#},
18222        base_text,
18223        &mut cx,
18224    );
18225    // Same for selections
18226    assert_hunk_revert(
18227        indoc! {r#"struct Row;
18228                   struct Row1;
18229                   struct Row2;
18230                   struct Row2.1;
18231                   struct Row2.2;
18232                   «ˇ
18233                   struct Row4;
18234                   struct» Row5;
18235                   «struct Row6;
18236                   ˇ»
18237                   struct Row9.1;
18238                   struct Row9.2;
18239                   struct Row9.3;
18240                   struct Row8;
18241                   struct Row9;
18242                   struct Row10;"#},
18243        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18244        indoc! {r#"struct Row;
18245                   struct Row1;
18246                   struct Row2;
18247                   struct Row2.1;
18248                   struct Row2.2;
18249                   «ˇ
18250                   struct Row4;
18251                   struct» Row5;
18252                   «struct Row6;
18253                   ˇ»
18254                   struct Row9.1;
18255                   struct Row9.2;
18256                   struct Row9.3;
18257                   struct Row8;
18258                   struct Row9;
18259                   struct Row10;"#},
18260        base_text,
18261        &mut cx,
18262    );
18263
18264    // When carets and selections intersect the addition hunks, those are reverted.
18265    // Adjacent carets got merged.
18266    assert_hunk_revert(
18267        indoc! {r#"struct Row;
18268                   ˇ// something on the top
18269                   struct Row1;
18270                   struct Row2;
18271                   struct Roˇw3.1;
18272                   struct Row2.2;
18273                   struct Row2.3;ˇ
18274
18275                   struct Row4;
18276                   struct ˇRow5.1;
18277                   struct Row5.2;
18278                   struct «Rowˇ»5.3;
18279                   struct Row5;
18280                   struct Row6;
18281                   ˇ
18282                   struct Row9.1;
18283                   struct «Rowˇ»9.2;
18284                   struct «ˇRow»9.3;
18285                   struct Row8;
18286                   struct Row9;
18287                   «ˇ// something on bottom»
18288                   struct Row10;"#},
18289        vec![
18290            DiffHunkStatusKind::Added,
18291            DiffHunkStatusKind::Added,
18292            DiffHunkStatusKind::Added,
18293            DiffHunkStatusKind::Added,
18294            DiffHunkStatusKind::Added,
18295        ],
18296        indoc! {r#"struct Row;
18297                   ˇstruct Row1;
18298                   struct Row2;
18299                   ˇ
18300                   struct Row4;
18301                   ˇstruct Row5;
18302                   struct Row6;
18303                   ˇ
18304                   ˇstruct Row8;
18305                   struct Row9;
18306                   ˇstruct Row10;"#},
18307        base_text,
18308        &mut cx,
18309    );
18310}
18311
18312#[gpui::test]
18313async fn test_modification_reverts(cx: &mut TestAppContext) {
18314    init_test(cx, |_| {});
18315    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18316    let base_text = indoc! {r#"
18317        struct Row;
18318        struct Row1;
18319        struct Row2;
18320
18321        struct Row4;
18322        struct Row5;
18323        struct Row6;
18324
18325        struct Row8;
18326        struct Row9;
18327        struct Row10;"#};
18328
18329    // Modification hunks behave the same as the addition ones.
18330    assert_hunk_revert(
18331        indoc! {r#"struct Row;
18332                   struct Row1;
18333                   struct Row33;
18334                   ˇ
18335                   struct Row4;
18336                   struct Row5;
18337                   struct Row6;
18338                   ˇ
18339                   struct Row99;
18340                   struct Row9;
18341                   struct Row10;"#},
18342        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18343        indoc! {r#"struct Row;
18344                   struct Row1;
18345                   struct Row33;
18346                   ˇ
18347                   struct Row4;
18348                   struct Row5;
18349                   struct Row6;
18350                   ˇ
18351                   struct Row99;
18352                   struct Row9;
18353                   struct Row10;"#},
18354        base_text,
18355        &mut cx,
18356    );
18357    assert_hunk_revert(
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        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18370        indoc! {r#"struct Row;
18371                   struct Row1;
18372                   struct Row33;
18373                   «ˇ
18374                   struct Row4;
18375                   struct» Row5;
18376                   «struct Row6;
18377                   ˇ»
18378                   struct Row99;
18379                   struct Row9;
18380                   struct Row10;"#},
18381        base_text,
18382        &mut cx,
18383    );
18384
18385    assert_hunk_revert(
18386        indoc! {r#"ˇstruct Row1.1;
18387                   struct Row1;
18388                   «ˇstr»uct Row22;
18389
18390                   struct ˇRow44;
18391                   struct Row5;
18392                   struct «Rˇ»ow66;ˇ
18393
18394                   «struˇ»ct Row88;
18395                   struct Row9;
18396                   struct Row1011;ˇ"#},
18397        vec![
18398            DiffHunkStatusKind::Modified,
18399            DiffHunkStatusKind::Modified,
18400            DiffHunkStatusKind::Modified,
18401            DiffHunkStatusKind::Modified,
18402            DiffHunkStatusKind::Modified,
18403            DiffHunkStatusKind::Modified,
18404        ],
18405        indoc! {r#"struct Row;
18406                   ˇstruct Row1;
18407                   struct Row2;
18408                   ˇ
18409                   struct Row4;
18410                   ˇstruct Row5;
18411                   struct Row6;
18412                   ˇ
18413                   struct Row8;
18414                   ˇstruct Row9;
18415                   struct Row10;ˇ"#},
18416        base_text,
18417        &mut cx,
18418    );
18419}
18420
18421#[gpui::test]
18422async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18423    init_test(cx, |_| {});
18424    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18425    let base_text = indoc! {r#"
18426        one
18427
18428        two
18429        three
18430        "#};
18431
18432    cx.set_head_text(base_text);
18433    cx.set_state("\nˇ\n");
18434    cx.executor().run_until_parked();
18435    cx.update_editor(|editor, _window, cx| {
18436        editor.expand_selected_diff_hunks(cx);
18437    });
18438    cx.executor().run_until_parked();
18439    cx.update_editor(|editor, window, cx| {
18440        editor.backspace(&Default::default(), window, cx);
18441    });
18442    cx.run_until_parked();
18443    cx.assert_state_with_diff(
18444        indoc! {r#"
18445
18446        - two
18447        - threeˇ
18448        +
18449        "#}
18450        .to_string(),
18451    );
18452}
18453
18454#[gpui::test]
18455async fn test_deletion_reverts(cx: &mut TestAppContext) {
18456    init_test(cx, |_| {});
18457    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18458    let base_text = indoc! {r#"struct Row;
18459struct Row1;
18460struct Row2;
18461
18462struct Row4;
18463struct Row5;
18464struct Row6;
18465
18466struct Row8;
18467struct Row9;
18468struct Row10;"#};
18469
18470    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18471    assert_hunk_revert(
18472        indoc! {r#"struct Row;
18473                   struct Row2;
18474
18475                   ˇstruct Row4;
18476                   struct Row5;
18477                   struct Row6;
18478                   ˇ
18479                   struct Row8;
18480                   struct Row10;"#},
18481        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18482        indoc! {r#"struct Row;
18483                   struct Row2;
18484
18485                   ˇstruct Row4;
18486                   struct Row5;
18487                   struct Row6;
18488                   ˇ
18489                   struct Row8;
18490                   struct Row10;"#},
18491        base_text,
18492        &mut cx,
18493    );
18494    assert_hunk_revert(
18495        indoc! {r#"struct Row;
18496                   struct Row2;
18497
18498                   «ˇstruct Row4;
18499                   struct» Row5;
18500                   «struct Row6;
18501                   ˇ»
18502                   struct Row8;
18503                   struct Row10;"#},
18504        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18505        indoc! {r#"struct Row;
18506                   struct Row2;
18507
18508                   «ˇstruct Row4;
18509                   struct» Row5;
18510                   «struct Row6;
18511                   ˇ»
18512                   struct Row8;
18513                   struct Row10;"#},
18514        base_text,
18515        &mut cx,
18516    );
18517
18518    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18519    assert_hunk_revert(
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        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18530        indoc! {r#"struct Row;
18531                   struct Row1;
18532                   ˇstruct Row2;
18533
18534                   struct Row4;
18535                   struct Row5;
18536                   struct Row6;
18537
18538                   struct Row8;ˇ
18539                   struct Row9;
18540                   struct Row10;"#},
18541        base_text,
18542        &mut cx,
18543    );
18544    assert_hunk_revert(
18545        indoc! {r#"struct Row;
18546                   struct Row2«ˇ;
18547                   struct Row4;
18548                   struct» Row5;
18549                   «struct Row6;
18550
18551                   struct Row8;ˇ»
18552                   struct Row10;"#},
18553        vec![
18554            DiffHunkStatusKind::Deleted,
18555            DiffHunkStatusKind::Deleted,
18556            DiffHunkStatusKind::Deleted,
18557        ],
18558        indoc! {r#"struct Row;
18559                   struct Row1;
18560                   struct Row2«ˇ;
18561
18562                   struct Row4;
18563                   struct» Row5;
18564                   «struct Row6;
18565
18566                   struct Row8;ˇ»
18567                   struct Row9;
18568                   struct Row10;"#},
18569        base_text,
18570        &mut cx,
18571    );
18572}
18573
18574#[gpui::test]
18575async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18576    init_test(cx, |_| {});
18577
18578    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18579    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18580    let base_text_3 =
18581        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18582
18583    let text_1 = edit_first_char_of_every_line(base_text_1);
18584    let text_2 = edit_first_char_of_every_line(base_text_2);
18585    let text_3 = edit_first_char_of_every_line(base_text_3);
18586
18587    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18588    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18589    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18590
18591    let multibuffer = cx.new(|cx| {
18592        let mut multibuffer = MultiBuffer::new(ReadWrite);
18593        multibuffer.push_excerpts(
18594            buffer_1.clone(),
18595            [
18596                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18597                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18598                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18599            ],
18600            cx,
18601        );
18602        multibuffer.push_excerpts(
18603            buffer_2.clone(),
18604            [
18605                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18606                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18607                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18608            ],
18609            cx,
18610        );
18611        multibuffer.push_excerpts(
18612            buffer_3.clone(),
18613            [
18614                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18615                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18616                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18617            ],
18618            cx,
18619        );
18620        multibuffer
18621    });
18622
18623    let fs = FakeFs::new(cx.executor());
18624    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
18625    let (editor, cx) = cx
18626        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
18627    editor.update_in(cx, |editor, _window, cx| {
18628        for (buffer, diff_base) in [
18629            (buffer_1.clone(), base_text_1),
18630            (buffer_2.clone(), base_text_2),
18631            (buffer_3.clone(), base_text_3),
18632        ] {
18633            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18634            editor
18635                .buffer
18636                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18637        }
18638    });
18639    cx.executor().run_until_parked();
18640
18641    editor.update_in(cx, |editor, window, cx| {
18642        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}");
18643        editor.select_all(&SelectAll, window, cx);
18644        editor.git_restore(&Default::default(), window, cx);
18645    });
18646    cx.executor().run_until_parked();
18647
18648    // When all ranges are selected, all buffer hunks are reverted.
18649    editor.update(cx, |editor, cx| {
18650        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");
18651    });
18652    buffer_1.update(cx, |buffer, _| {
18653        assert_eq!(buffer.text(), base_text_1);
18654    });
18655    buffer_2.update(cx, |buffer, _| {
18656        assert_eq!(buffer.text(), base_text_2);
18657    });
18658    buffer_3.update(cx, |buffer, _| {
18659        assert_eq!(buffer.text(), base_text_3);
18660    });
18661
18662    editor.update_in(cx, |editor, window, cx| {
18663        editor.undo(&Default::default(), window, cx);
18664    });
18665
18666    editor.update_in(cx, |editor, window, cx| {
18667        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18668            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
18669        });
18670        editor.git_restore(&Default::default(), window, cx);
18671    });
18672
18673    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
18674    // but not affect buffer_2 and its related excerpts.
18675    editor.update(cx, |editor, cx| {
18676        assert_eq!(
18677            editor.text(cx),
18678            "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}"
18679        );
18680    });
18681    buffer_1.update(cx, |buffer, _| {
18682        assert_eq!(buffer.text(), base_text_1);
18683    });
18684    buffer_2.update(cx, |buffer, _| {
18685        assert_eq!(
18686            buffer.text(),
18687            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
18688        );
18689    });
18690    buffer_3.update(cx, |buffer, _| {
18691        assert_eq!(
18692            buffer.text(),
18693            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
18694        );
18695    });
18696
18697    fn edit_first_char_of_every_line(text: &str) -> String {
18698        text.split('\n')
18699            .map(|line| format!("X{}", &line[1..]))
18700            .collect::<Vec<_>>()
18701            .join("\n")
18702    }
18703}
18704
18705#[gpui::test]
18706async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
18707    init_test(cx, |_| {});
18708
18709    let cols = 4;
18710    let rows = 10;
18711    let sample_text_1 = sample_text(rows, cols, 'a');
18712    assert_eq!(
18713        sample_text_1,
18714        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
18715    );
18716    let sample_text_2 = sample_text(rows, cols, 'l');
18717    assert_eq!(
18718        sample_text_2,
18719        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
18720    );
18721    let sample_text_3 = sample_text(rows, cols, 'v');
18722    assert_eq!(
18723        sample_text_3,
18724        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
18725    );
18726
18727    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
18728    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
18729    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
18730
18731    let multi_buffer = cx.new(|cx| {
18732        let mut multibuffer = MultiBuffer::new(ReadWrite);
18733        multibuffer.push_excerpts(
18734            buffer_1.clone(),
18735            [
18736                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18737                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18738                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18739            ],
18740            cx,
18741        );
18742        multibuffer.push_excerpts(
18743            buffer_2.clone(),
18744            [
18745                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18746                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18747                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18748            ],
18749            cx,
18750        );
18751        multibuffer.push_excerpts(
18752            buffer_3.clone(),
18753            [
18754                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18755                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18756                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18757            ],
18758            cx,
18759        );
18760        multibuffer
18761    });
18762
18763    let fs = FakeFs::new(cx.executor());
18764    fs.insert_tree(
18765        "/a",
18766        json!({
18767            "main.rs": sample_text_1,
18768            "other.rs": sample_text_2,
18769            "lib.rs": sample_text_3,
18770        }),
18771    )
18772    .await;
18773    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18774    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18775    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18776    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18777        Editor::new(
18778            EditorMode::full(),
18779            multi_buffer,
18780            Some(project.clone()),
18781            window,
18782            cx,
18783        )
18784    });
18785    let multibuffer_item_id = workspace
18786        .update(cx, |workspace, window, cx| {
18787            assert!(
18788                workspace.active_item(cx).is_none(),
18789                "active item should be None before the first item is added"
18790            );
18791            workspace.add_item_to_active_pane(
18792                Box::new(multi_buffer_editor.clone()),
18793                None,
18794                true,
18795                window,
18796                cx,
18797            );
18798            let active_item = workspace
18799                .active_item(cx)
18800                .expect("should have an active item after adding the multi buffer");
18801            assert!(
18802                !active_item.is_singleton(cx),
18803                "A multi buffer was expected to active after adding"
18804            );
18805            active_item.item_id()
18806        })
18807        .unwrap();
18808    cx.executor().run_until_parked();
18809
18810    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18811        editor.change_selections(
18812            SelectionEffects::scroll(Autoscroll::Next),
18813            window,
18814            cx,
18815            |s| s.select_ranges(Some(1..2)),
18816        );
18817        editor.open_excerpts(&OpenExcerpts, window, cx);
18818    });
18819    cx.executor().run_until_parked();
18820    let first_item_id = workspace
18821        .update(cx, |workspace, window, cx| {
18822            let active_item = workspace
18823                .active_item(cx)
18824                .expect("should have an active item after navigating into the 1st buffer");
18825            let first_item_id = active_item.item_id();
18826            assert_ne!(
18827                first_item_id, multibuffer_item_id,
18828                "Should navigate into the 1st buffer and activate it"
18829            );
18830            assert!(
18831                active_item.is_singleton(cx),
18832                "New active item should be a singleton buffer"
18833            );
18834            assert_eq!(
18835                active_item
18836                    .act_as::<Editor>(cx)
18837                    .expect("should have navigated into an editor for the 1st buffer")
18838                    .read(cx)
18839                    .text(cx),
18840                sample_text_1
18841            );
18842
18843            workspace
18844                .go_back(workspace.active_pane().downgrade(), window, cx)
18845                .detach_and_log_err(cx);
18846
18847            first_item_id
18848        })
18849        .unwrap();
18850    cx.executor().run_until_parked();
18851    workspace
18852        .update(cx, |workspace, _, cx| {
18853            let active_item = workspace
18854                .active_item(cx)
18855                .expect("should have an active item after navigating back");
18856            assert_eq!(
18857                active_item.item_id(),
18858                multibuffer_item_id,
18859                "Should navigate back to the multi buffer"
18860            );
18861            assert!(!active_item.is_singleton(cx));
18862        })
18863        .unwrap();
18864
18865    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18866        editor.change_selections(
18867            SelectionEffects::scroll(Autoscroll::Next),
18868            window,
18869            cx,
18870            |s| s.select_ranges(Some(39..40)),
18871        );
18872        editor.open_excerpts(&OpenExcerpts, window, cx);
18873    });
18874    cx.executor().run_until_parked();
18875    let second_item_id = workspace
18876        .update(cx, |workspace, window, cx| {
18877            let active_item = workspace
18878                .active_item(cx)
18879                .expect("should have an active item after navigating into the 2nd buffer");
18880            let second_item_id = active_item.item_id();
18881            assert_ne!(
18882                second_item_id, multibuffer_item_id,
18883                "Should navigate away from the multibuffer"
18884            );
18885            assert_ne!(
18886                second_item_id, first_item_id,
18887                "Should navigate into the 2nd buffer and activate it"
18888            );
18889            assert!(
18890                active_item.is_singleton(cx),
18891                "New active item should be a singleton buffer"
18892            );
18893            assert_eq!(
18894                active_item
18895                    .act_as::<Editor>(cx)
18896                    .expect("should have navigated into an editor")
18897                    .read(cx)
18898                    .text(cx),
18899                sample_text_2
18900            );
18901
18902            workspace
18903                .go_back(workspace.active_pane().downgrade(), window, cx)
18904                .detach_and_log_err(cx);
18905
18906            second_item_id
18907        })
18908        .unwrap();
18909    cx.executor().run_until_parked();
18910    workspace
18911        .update(cx, |workspace, _, cx| {
18912            let active_item = workspace
18913                .active_item(cx)
18914                .expect("should have an active item after navigating back from the 2nd buffer");
18915            assert_eq!(
18916                active_item.item_id(),
18917                multibuffer_item_id,
18918                "Should navigate back from the 2nd buffer to the multi buffer"
18919            );
18920            assert!(!active_item.is_singleton(cx));
18921        })
18922        .unwrap();
18923
18924    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18925        editor.change_selections(
18926            SelectionEffects::scroll(Autoscroll::Next),
18927            window,
18928            cx,
18929            |s| s.select_ranges(Some(70..70)),
18930        );
18931        editor.open_excerpts(&OpenExcerpts, window, cx);
18932    });
18933    cx.executor().run_until_parked();
18934    workspace
18935        .update(cx, |workspace, window, cx| {
18936            let active_item = workspace
18937                .active_item(cx)
18938                .expect("should have an active item after navigating into the 3rd buffer");
18939            let third_item_id = active_item.item_id();
18940            assert_ne!(
18941                third_item_id, multibuffer_item_id,
18942                "Should navigate into the 3rd buffer and activate it"
18943            );
18944            assert_ne!(third_item_id, first_item_id);
18945            assert_ne!(third_item_id, second_item_id);
18946            assert!(
18947                active_item.is_singleton(cx),
18948                "New active item should be a singleton buffer"
18949            );
18950            assert_eq!(
18951                active_item
18952                    .act_as::<Editor>(cx)
18953                    .expect("should have navigated into an editor")
18954                    .read(cx)
18955                    .text(cx),
18956                sample_text_3
18957            );
18958
18959            workspace
18960                .go_back(workspace.active_pane().downgrade(), window, cx)
18961                .detach_and_log_err(cx);
18962        })
18963        .unwrap();
18964    cx.executor().run_until_parked();
18965    workspace
18966        .update(cx, |workspace, _, cx| {
18967            let active_item = workspace
18968                .active_item(cx)
18969                .expect("should have an active item after navigating back from the 3rd buffer");
18970            assert_eq!(
18971                active_item.item_id(),
18972                multibuffer_item_id,
18973                "Should navigate back from the 3rd buffer to the multi buffer"
18974            );
18975            assert!(!active_item.is_singleton(cx));
18976        })
18977        .unwrap();
18978}
18979
18980#[gpui::test]
18981async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18982    init_test(cx, |_| {});
18983
18984    let mut cx = EditorTestContext::new(cx).await;
18985
18986    let diff_base = r#"
18987        use some::mod;
18988
18989        const A: u32 = 42;
18990
18991        fn main() {
18992            println!("hello");
18993
18994            println!("world");
18995        }
18996        "#
18997    .unindent();
18998
18999    cx.set_state(
19000        &r#"
19001        use some::modified;
19002
19003        ˇ
19004        fn main() {
19005            println!("hello there");
19006
19007            println!("around the");
19008            println!("world");
19009        }
19010        "#
19011        .unindent(),
19012    );
19013
19014    cx.set_head_text(&diff_base);
19015    executor.run_until_parked();
19016
19017    cx.update_editor(|editor, window, cx| {
19018        editor.go_to_next_hunk(&GoToHunk, window, cx);
19019        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19020    });
19021    executor.run_until_parked();
19022    cx.assert_state_with_diff(
19023        r#"
19024          use some::modified;
19025
19026
19027          fn main() {
19028        -     println!("hello");
19029        + ˇ    println!("hello there");
19030
19031              println!("around the");
19032              println!("world");
19033          }
19034        "#
19035        .unindent(),
19036    );
19037
19038    cx.update_editor(|editor, window, cx| {
19039        for _ in 0..2 {
19040            editor.go_to_next_hunk(&GoToHunk, window, cx);
19041            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19042        }
19043    });
19044    executor.run_until_parked();
19045    cx.assert_state_with_diff(
19046        r#"
19047        - use some::mod;
19048        + ˇuse some::modified;
19049
19050
19051          fn main() {
19052        -     println!("hello");
19053        +     println!("hello there");
19054
19055        +     println!("around the");
19056              println!("world");
19057          }
19058        "#
19059        .unindent(),
19060    );
19061
19062    cx.update_editor(|editor, window, cx| {
19063        editor.go_to_next_hunk(&GoToHunk, window, cx);
19064        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19065    });
19066    executor.run_until_parked();
19067    cx.assert_state_with_diff(
19068        r#"
19069        - use some::mod;
19070        + use some::modified;
19071
19072        - const A: u32 = 42;
19073          ˇ
19074          fn main() {
19075        -     println!("hello");
19076        +     println!("hello there");
19077
19078        +     println!("around the");
19079              println!("world");
19080          }
19081        "#
19082        .unindent(),
19083    );
19084
19085    cx.update_editor(|editor, window, cx| {
19086        editor.cancel(&Cancel, window, cx);
19087    });
19088
19089    cx.assert_state_with_diff(
19090        r#"
19091          use some::modified;
19092
19093          ˇ
19094          fn main() {
19095              println!("hello there");
19096
19097              println!("around the");
19098              println!("world");
19099          }
19100        "#
19101        .unindent(),
19102    );
19103}
19104
19105#[gpui::test]
19106async fn test_diff_base_change_with_expanded_diff_hunks(
19107    executor: BackgroundExecutor,
19108    cx: &mut TestAppContext,
19109) {
19110    init_test(cx, |_| {});
19111
19112    let mut cx = EditorTestContext::new(cx).await;
19113
19114    let diff_base = r#"
19115        use some::mod1;
19116        use some::mod2;
19117
19118        const A: u32 = 42;
19119        const B: u32 = 42;
19120        const C: u32 = 42;
19121
19122        fn main() {
19123            println!("hello");
19124
19125            println!("world");
19126        }
19127        "#
19128    .unindent();
19129
19130    cx.set_state(
19131        &r#"
19132        use some::mod2;
19133
19134        const A: u32 = 42;
19135        const C: u32 = 42;
19136
19137        fn main(ˇ) {
19138            //println!("hello");
19139
19140            println!("world");
19141            //
19142            //
19143        }
19144        "#
19145        .unindent(),
19146    );
19147
19148    cx.set_head_text(&diff_base);
19149    executor.run_until_parked();
19150
19151    cx.update_editor(|editor, window, cx| {
19152        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19153    });
19154    executor.run_until_parked();
19155    cx.assert_state_with_diff(
19156        r#"
19157        - use some::mod1;
19158          use some::mod2;
19159
19160          const A: u32 = 42;
19161        - const B: u32 = 42;
19162          const C: u32 = 42;
19163
19164          fn main(ˇ) {
19165        -     println!("hello");
19166        +     //println!("hello");
19167
19168              println!("world");
19169        +     //
19170        +     //
19171          }
19172        "#
19173        .unindent(),
19174    );
19175
19176    cx.set_head_text("new diff base!");
19177    executor.run_until_parked();
19178    cx.assert_state_with_diff(
19179        r#"
19180        - new diff base!
19181        + use some::mod2;
19182        +
19183        + const A: u32 = 42;
19184        + const C: u32 = 42;
19185        +
19186        + fn main(ˇ) {
19187        +     //println!("hello");
19188        +
19189        +     println!("world");
19190        +     //
19191        +     //
19192        + }
19193        "#
19194        .unindent(),
19195    );
19196}
19197
19198#[gpui::test]
19199async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19200    init_test(cx, |_| {});
19201
19202    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19203    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19204    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19205    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19206    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19207    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19208
19209    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19210    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19211    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19212
19213    let multi_buffer = cx.new(|cx| {
19214        let mut multibuffer = MultiBuffer::new(ReadWrite);
19215        multibuffer.push_excerpts(
19216            buffer_1.clone(),
19217            [
19218                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19219                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19220                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19221            ],
19222            cx,
19223        );
19224        multibuffer.push_excerpts(
19225            buffer_2.clone(),
19226            [
19227                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19228                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19229                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19230            ],
19231            cx,
19232        );
19233        multibuffer.push_excerpts(
19234            buffer_3.clone(),
19235            [
19236                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19237                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19238                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19239            ],
19240            cx,
19241        );
19242        multibuffer
19243    });
19244
19245    let editor =
19246        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19247    editor
19248        .update(cx, |editor, _window, cx| {
19249            for (buffer, diff_base) in [
19250                (buffer_1.clone(), file_1_old),
19251                (buffer_2.clone(), file_2_old),
19252                (buffer_3.clone(), file_3_old),
19253            ] {
19254                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19255                editor
19256                    .buffer
19257                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19258            }
19259        })
19260        .unwrap();
19261
19262    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19263    cx.run_until_parked();
19264
19265    cx.assert_editor_state(
19266        &"
19267            ˇaaa
19268            ccc
19269            ddd
19270
19271            ggg
19272            hhh
19273
19274
19275            lll
19276            mmm
19277            NNN
19278
19279            qqq
19280            rrr
19281
19282            uuu
19283            111
19284            222
19285            333
19286
19287            666
19288            777
19289
19290            000
19291            !!!"
19292        .unindent(),
19293    );
19294
19295    cx.update_editor(|editor, window, cx| {
19296        editor.select_all(&SelectAll, window, cx);
19297        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19298    });
19299    cx.executor().run_until_parked();
19300
19301    cx.assert_state_with_diff(
19302        "
19303            «aaa
19304          - bbb
19305            ccc
19306            ddd
19307
19308            ggg
19309            hhh
19310
19311
19312            lll
19313            mmm
19314          - nnn
19315          + NNN
19316
19317            qqq
19318            rrr
19319
19320            uuu
19321            111
19322            222
19323            333
19324
19325          + 666
19326            777
19327
19328            000
19329            !!!ˇ»"
19330            .unindent(),
19331    );
19332}
19333
19334#[gpui::test]
19335async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19336    init_test(cx, |_| {});
19337
19338    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19339    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19340
19341    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19342    let multi_buffer = cx.new(|cx| {
19343        let mut multibuffer = MultiBuffer::new(ReadWrite);
19344        multibuffer.push_excerpts(
19345            buffer.clone(),
19346            [
19347                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19348                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19349                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19350            ],
19351            cx,
19352        );
19353        multibuffer
19354    });
19355
19356    let editor =
19357        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19358    editor
19359        .update(cx, |editor, _window, cx| {
19360            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19361            editor
19362                .buffer
19363                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19364        })
19365        .unwrap();
19366
19367    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19368    cx.run_until_parked();
19369
19370    cx.update_editor(|editor, window, cx| {
19371        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19372    });
19373    cx.executor().run_until_parked();
19374
19375    // When the start of a hunk coincides with the start of its excerpt,
19376    // the hunk is expanded. When the start of a hunk is earlier than
19377    // the start of its excerpt, the hunk is not expanded.
19378    cx.assert_state_with_diff(
19379        "
19380            ˇaaa
19381          - bbb
19382          + BBB
19383
19384          - ddd
19385          - eee
19386          + DDD
19387          + EEE
19388            fff
19389
19390            iii
19391        "
19392        .unindent(),
19393    );
19394}
19395
19396#[gpui::test]
19397async fn test_edits_around_expanded_insertion_hunks(
19398    executor: BackgroundExecutor,
19399    cx: &mut TestAppContext,
19400) {
19401    init_test(cx, |_| {});
19402
19403    let mut cx = EditorTestContext::new(cx).await;
19404
19405    let diff_base = r#"
19406        use some::mod1;
19407        use some::mod2;
19408
19409        const A: u32 = 42;
19410
19411        fn main() {
19412            println!("hello");
19413
19414            println!("world");
19415        }
19416        "#
19417    .unindent();
19418    executor.run_until_parked();
19419    cx.set_state(
19420        &r#"
19421        use some::mod1;
19422        use some::mod2;
19423
19424        const A: u32 = 42;
19425        const B: u32 = 42;
19426        const C: u32 = 42;
19427        ˇ
19428
19429        fn main() {
19430            println!("hello");
19431
19432            println!("world");
19433        }
19434        "#
19435        .unindent(),
19436    );
19437
19438    cx.set_head_text(&diff_base);
19439    executor.run_until_parked();
19440
19441    cx.update_editor(|editor, window, cx| {
19442        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19443    });
19444    executor.run_until_parked();
19445
19446    cx.assert_state_with_diff(
19447        r#"
19448        use some::mod1;
19449        use some::mod2;
19450
19451        const A: u32 = 42;
19452      + const B: u32 = 42;
19453      + const C: u32 = 42;
19454      + ˇ
19455
19456        fn main() {
19457            println!("hello");
19458
19459            println!("world");
19460        }
19461      "#
19462        .unindent(),
19463    );
19464
19465    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19466    executor.run_until_parked();
19467
19468    cx.assert_state_with_diff(
19469        r#"
19470        use some::mod1;
19471        use some::mod2;
19472
19473        const A: u32 = 42;
19474      + const B: u32 = 42;
19475      + const C: u32 = 42;
19476      + const D: u32 = 42;
19477      + ˇ
19478
19479        fn main() {
19480            println!("hello");
19481
19482            println!("world");
19483        }
19484      "#
19485        .unindent(),
19486    );
19487
19488    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19489    executor.run_until_parked();
19490
19491    cx.assert_state_with_diff(
19492        r#"
19493        use some::mod1;
19494        use some::mod2;
19495
19496        const A: u32 = 42;
19497      + const B: u32 = 42;
19498      + const C: u32 = 42;
19499      + const D: u32 = 42;
19500      + const E: u32 = 42;
19501      + ˇ
19502
19503        fn main() {
19504            println!("hello");
19505
19506            println!("world");
19507        }
19508      "#
19509        .unindent(),
19510    );
19511
19512    cx.update_editor(|editor, window, cx| {
19513        editor.delete_line(&DeleteLine, window, cx);
19514    });
19515    executor.run_until_parked();
19516
19517    cx.assert_state_with_diff(
19518        r#"
19519        use some::mod1;
19520        use some::mod2;
19521
19522        const A: u32 = 42;
19523      + const B: u32 = 42;
19524      + const C: u32 = 42;
19525      + const D: u32 = 42;
19526      + const E: u32 = 42;
19527        ˇ
19528        fn main() {
19529            println!("hello");
19530
19531            println!("world");
19532        }
19533      "#
19534        .unindent(),
19535    );
19536
19537    cx.update_editor(|editor, window, cx| {
19538        editor.move_up(&MoveUp, window, cx);
19539        editor.delete_line(&DeleteLine, window, cx);
19540        editor.move_up(&MoveUp, window, cx);
19541        editor.delete_line(&DeleteLine, window, cx);
19542        editor.move_up(&MoveUp, window, cx);
19543        editor.delete_line(&DeleteLine, window, cx);
19544    });
19545    executor.run_until_parked();
19546    cx.assert_state_with_diff(
19547        r#"
19548        use some::mod1;
19549        use some::mod2;
19550
19551        const A: u32 = 42;
19552      + const B: u32 = 42;
19553        ˇ
19554        fn main() {
19555            println!("hello");
19556
19557            println!("world");
19558        }
19559      "#
19560        .unindent(),
19561    );
19562
19563    cx.update_editor(|editor, window, cx| {
19564        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19565        editor.delete_line(&DeleteLine, window, cx);
19566    });
19567    executor.run_until_parked();
19568    cx.assert_state_with_diff(
19569        r#"
19570        ˇ
19571        fn main() {
19572            println!("hello");
19573
19574            println!("world");
19575        }
19576      "#
19577        .unindent(),
19578    );
19579}
19580
19581#[gpui::test]
19582async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19583    init_test(cx, |_| {});
19584
19585    let mut cx = EditorTestContext::new(cx).await;
19586    cx.set_head_text(indoc! { "
19587        one
19588        two
19589        three
19590        four
19591        five
19592        "
19593    });
19594    cx.set_state(indoc! { "
19595        one
19596        ˇthree
19597        five
19598    "});
19599    cx.run_until_parked();
19600    cx.update_editor(|editor, window, cx| {
19601        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19602    });
19603    cx.assert_state_with_diff(
19604        indoc! { "
19605        one
19606      - two
19607        ˇthree
19608      - four
19609        five
19610    "}
19611        .to_string(),
19612    );
19613    cx.update_editor(|editor, window, cx| {
19614        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19615    });
19616
19617    cx.assert_state_with_diff(
19618        indoc! { "
19619        one
19620        ˇthree
19621        five
19622    "}
19623        .to_string(),
19624    );
19625
19626    cx.set_state(indoc! { "
19627        one
19628        ˇTWO
19629        three
19630        four
19631        five
19632    "});
19633    cx.run_until_parked();
19634    cx.update_editor(|editor, window, cx| {
19635        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19636    });
19637
19638    cx.assert_state_with_diff(
19639        indoc! { "
19640            one
19641          - two
19642          + ˇTWO
19643            three
19644            four
19645            five
19646        "}
19647        .to_string(),
19648    );
19649    cx.update_editor(|editor, window, cx| {
19650        editor.move_up(&Default::default(), window, cx);
19651        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19652    });
19653    cx.assert_state_with_diff(
19654        indoc! { "
19655            one
19656            ˇTWO
19657            three
19658            four
19659            five
19660        "}
19661        .to_string(),
19662    );
19663}
19664
19665#[gpui::test]
19666async fn test_edits_around_expanded_deletion_hunks(
19667    executor: BackgroundExecutor,
19668    cx: &mut TestAppContext,
19669) {
19670    init_test(cx, |_| {});
19671
19672    let mut cx = EditorTestContext::new(cx).await;
19673
19674    let diff_base = r#"
19675        use some::mod1;
19676        use some::mod2;
19677
19678        const A: u32 = 42;
19679        const B: u32 = 42;
19680        const C: u32 = 42;
19681
19682
19683        fn main() {
19684            println!("hello");
19685
19686            println!("world");
19687        }
19688    "#
19689    .unindent();
19690    executor.run_until_parked();
19691    cx.set_state(
19692        &r#"
19693        use some::mod1;
19694        use some::mod2;
19695
19696        ˇconst B: u32 = 42;
19697        const C: u32 = 42;
19698
19699
19700        fn main() {
19701            println!("hello");
19702
19703            println!("world");
19704        }
19705        "#
19706        .unindent(),
19707    );
19708
19709    cx.set_head_text(&diff_base);
19710    executor.run_until_parked();
19711
19712    cx.update_editor(|editor, window, cx| {
19713        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19714    });
19715    executor.run_until_parked();
19716
19717    cx.assert_state_with_diff(
19718        r#"
19719        use some::mod1;
19720        use some::mod2;
19721
19722      - const A: u32 = 42;
19723        ˇconst B: u32 = 42;
19724        const C: u32 = 42;
19725
19726
19727        fn main() {
19728            println!("hello");
19729
19730            println!("world");
19731        }
19732      "#
19733        .unindent(),
19734    );
19735
19736    cx.update_editor(|editor, window, cx| {
19737        editor.delete_line(&DeleteLine, window, cx);
19738    });
19739    executor.run_until_parked();
19740    cx.assert_state_with_diff(
19741        r#"
19742        use some::mod1;
19743        use some::mod2;
19744
19745      - const A: u32 = 42;
19746      - const B: u32 = 42;
19747        ˇconst C: u32 = 42;
19748
19749
19750        fn main() {
19751            println!("hello");
19752
19753            println!("world");
19754        }
19755      "#
19756        .unindent(),
19757    );
19758
19759    cx.update_editor(|editor, window, cx| {
19760        editor.delete_line(&DeleteLine, window, cx);
19761    });
19762    executor.run_until_parked();
19763    cx.assert_state_with_diff(
19764        r#"
19765        use some::mod1;
19766        use some::mod2;
19767
19768      - const A: u32 = 42;
19769      - const B: u32 = 42;
19770      - const C: u32 = 42;
19771        ˇ
19772
19773        fn main() {
19774            println!("hello");
19775
19776            println!("world");
19777        }
19778      "#
19779        .unindent(),
19780    );
19781
19782    cx.update_editor(|editor, window, cx| {
19783        editor.handle_input("replacement", window, cx);
19784    });
19785    executor.run_until_parked();
19786    cx.assert_state_with_diff(
19787        r#"
19788        use some::mod1;
19789        use some::mod2;
19790
19791      - const A: u32 = 42;
19792      - const B: u32 = 42;
19793      - const C: u32 = 42;
19794      -
19795      + replacementˇ
19796
19797        fn main() {
19798            println!("hello");
19799
19800            println!("world");
19801        }
19802      "#
19803        .unindent(),
19804    );
19805}
19806
19807#[gpui::test]
19808async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19809    init_test(cx, |_| {});
19810
19811    let mut cx = EditorTestContext::new(cx).await;
19812
19813    let base_text = r#"
19814        one
19815        two
19816        three
19817        four
19818        five
19819    "#
19820    .unindent();
19821    executor.run_until_parked();
19822    cx.set_state(
19823        &r#"
19824        one
19825        two
19826        fˇour
19827        five
19828        "#
19829        .unindent(),
19830    );
19831
19832    cx.set_head_text(&base_text);
19833    executor.run_until_parked();
19834
19835    cx.update_editor(|editor, window, cx| {
19836        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19837    });
19838    executor.run_until_parked();
19839
19840    cx.assert_state_with_diff(
19841        r#"
19842          one
19843          two
19844        - three
19845          fˇour
19846          five
19847        "#
19848        .unindent(),
19849    );
19850
19851    cx.update_editor(|editor, window, cx| {
19852        editor.backspace(&Backspace, window, cx);
19853        editor.backspace(&Backspace, window, cx);
19854    });
19855    executor.run_until_parked();
19856    cx.assert_state_with_diff(
19857        r#"
19858          one
19859          two
19860        - threeˇ
19861        - four
19862        + our
19863          five
19864        "#
19865        .unindent(),
19866    );
19867}
19868
19869#[gpui::test]
19870async fn test_edit_after_expanded_modification_hunk(
19871    executor: BackgroundExecutor,
19872    cx: &mut TestAppContext,
19873) {
19874    init_test(cx, |_| {});
19875
19876    let mut cx = EditorTestContext::new(cx).await;
19877
19878    let diff_base = r#"
19879        use some::mod1;
19880        use some::mod2;
19881
19882        const A: u32 = 42;
19883        const B: u32 = 42;
19884        const C: u32 = 42;
19885        const D: u32 = 42;
19886
19887
19888        fn main() {
19889            println!("hello");
19890
19891            println!("world");
19892        }"#
19893    .unindent();
19894
19895    cx.set_state(
19896        &r#"
19897        use some::mod1;
19898        use some::mod2;
19899
19900        const A: u32 = 42;
19901        const B: u32 = 42;
19902        const C: u32 = 43ˇ
19903        const D: u32 = 42;
19904
19905
19906        fn main() {
19907            println!("hello");
19908
19909            println!("world");
19910        }"#
19911        .unindent(),
19912    );
19913
19914    cx.set_head_text(&diff_base);
19915    executor.run_until_parked();
19916    cx.update_editor(|editor, window, cx| {
19917        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19918    });
19919    executor.run_until_parked();
19920
19921    cx.assert_state_with_diff(
19922        r#"
19923        use some::mod1;
19924        use some::mod2;
19925
19926        const A: u32 = 42;
19927        const B: u32 = 42;
19928      - const C: u32 = 42;
19929      + const C: u32 = 43ˇ
19930        const D: u32 = 42;
19931
19932
19933        fn main() {
19934            println!("hello");
19935
19936            println!("world");
19937        }"#
19938        .unindent(),
19939    );
19940
19941    cx.update_editor(|editor, window, cx| {
19942        editor.handle_input("\nnew_line\n", window, cx);
19943    });
19944    executor.run_until_parked();
19945
19946    cx.assert_state_with_diff(
19947        r#"
19948        use some::mod1;
19949        use some::mod2;
19950
19951        const A: u32 = 42;
19952        const B: u32 = 42;
19953      - const C: u32 = 42;
19954      + const C: u32 = 43
19955      + new_line
19956      + ˇ
19957        const D: u32 = 42;
19958
19959
19960        fn main() {
19961            println!("hello");
19962
19963            println!("world");
19964        }"#
19965        .unindent(),
19966    );
19967}
19968
19969#[gpui::test]
19970async fn test_stage_and_unstage_added_file_hunk(
19971    executor: BackgroundExecutor,
19972    cx: &mut TestAppContext,
19973) {
19974    init_test(cx, |_| {});
19975
19976    let mut cx = EditorTestContext::new(cx).await;
19977    cx.update_editor(|editor, _, cx| {
19978        editor.set_expand_all_diff_hunks(cx);
19979    });
19980
19981    let working_copy = r#"
19982            ˇfn main() {
19983                println!("hello, world!");
19984            }
19985        "#
19986    .unindent();
19987
19988    cx.set_state(&working_copy);
19989    executor.run_until_parked();
19990
19991    cx.assert_state_with_diff(
19992        r#"
19993            + ˇfn main() {
19994            +     println!("hello, world!");
19995            + }
19996        "#
19997        .unindent(),
19998    );
19999    cx.assert_index_text(None);
20000
20001    cx.update_editor(|editor, window, cx| {
20002        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20003    });
20004    executor.run_until_parked();
20005    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
20006    cx.assert_state_with_diff(
20007        r#"
20008            + ˇfn main() {
20009            +     println!("hello, world!");
20010            + }
20011        "#
20012        .unindent(),
20013    );
20014
20015    cx.update_editor(|editor, window, cx| {
20016        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20017    });
20018    executor.run_until_parked();
20019    cx.assert_index_text(None);
20020}
20021
20022async fn setup_indent_guides_editor(
20023    text: &str,
20024    cx: &mut TestAppContext,
20025) -> (BufferId, EditorTestContext) {
20026    init_test(cx, |_| {});
20027
20028    let mut cx = EditorTestContext::new(cx).await;
20029
20030    let buffer_id = cx.update_editor(|editor, window, cx| {
20031        editor.set_text(text, window, cx);
20032        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
20033
20034        buffer_ids[0]
20035    });
20036
20037    (buffer_id, cx)
20038}
20039
20040fn assert_indent_guides(
20041    range: Range<u32>,
20042    expected: Vec<IndentGuide>,
20043    active_indices: Option<Vec<usize>>,
20044    cx: &mut EditorTestContext,
20045) {
20046    let indent_guides = cx.update_editor(|editor, window, cx| {
20047        let snapshot = editor.snapshot(window, cx).display_snapshot;
20048        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
20049            editor,
20050            MultiBufferRow(range.start)..MultiBufferRow(range.end),
20051            true,
20052            &snapshot,
20053            cx,
20054        );
20055
20056        indent_guides.sort_by(|a, b| {
20057            a.depth.cmp(&b.depth).then(
20058                a.start_row
20059                    .cmp(&b.start_row)
20060                    .then(a.end_row.cmp(&b.end_row)),
20061            )
20062        });
20063        indent_guides
20064    });
20065
20066    if let Some(expected) = active_indices {
20067        let active_indices = cx.update_editor(|editor, window, cx| {
20068            let snapshot = editor.snapshot(window, cx).display_snapshot;
20069            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
20070        });
20071
20072        assert_eq!(
20073            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
20074            expected,
20075            "Active indent guide indices do not match"
20076        );
20077    }
20078
20079    assert_eq!(indent_guides, expected, "Indent guides do not match");
20080}
20081
20082fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
20083    IndentGuide {
20084        buffer_id,
20085        start_row: MultiBufferRow(start_row),
20086        end_row: MultiBufferRow(end_row),
20087        depth,
20088        tab_size: 4,
20089        settings: IndentGuideSettings {
20090            enabled: true,
20091            line_width: 1,
20092            active_line_width: 1,
20093            coloring: IndentGuideColoring::default(),
20094            background_coloring: IndentGuideBackgroundColoring::default(),
20095        },
20096    }
20097}
20098
20099#[gpui::test]
20100async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
20101    let (buffer_id, mut cx) = setup_indent_guides_editor(
20102        &"
20103        fn main() {
20104            let a = 1;
20105        }"
20106        .unindent(),
20107        cx,
20108    )
20109    .await;
20110
20111    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20112}
20113
20114#[gpui::test]
20115async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
20116    let (buffer_id, mut cx) = setup_indent_guides_editor(
20117        &"
20118        fn main() {
20119            let a = 1;
20120            let b = 2;
20121        }"
20122        .unindent(),
20123        cx,
20124    )
20125    .await;
20126
20127    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20128}
20129
20130#[gpui::test]
20131async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20132    let (buffer_id, mut cx) = setup_indent_guides_editor(
20133        &"
20134        fn main() {
20135            let a = 1;
20136            if a == 3 {
20137                let b = 2;
20138            } else {
20139                let c = 3;
20140            }
20141        }"
20142        .unindent(),
20143        cx,
20144    )
20145    .await;
20146
20147    assert_indent_guides(
20148        0..8,
20149        vec![
20150            indent_guide(buffer_id, 1, 6, 0),
20151            indent_guide(buffer_id, 3, 3, 1),
20152            indent_guide(buffer_id, 5, 5, 1),
20153        ],
20154        None,
20155        &mut cx,
20156    );
20157}
20158
20159#[gpui::test]
20160async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20161    let (buffer_id, mut cx) = setup_indent_guides_editor(
20162        &"
20163        fn main() {
20164            let a = 1;
20165                let b = 2;
20166            let c = 3;
20167        }"
20168        .unindent(),
20169        cx,
20170    )
20171    .await;
20172
20173    assert_indent_guides(
20174        0..5,
20175        vec![
20176            indent_guide(buffer_id, 1, 3, 0),
20177            indent_guide(buffer_id, 2, 2, 1),
20178        ],
20179        None,
20180        &mut cx,
20181    );
20182}
20183
20184#[gpui::test]
20185async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20186    let (buffer_id, mut cx) = setup_indent_guides_editor(
20187        &"
20188        fn main() {
20189            let a = 1;
20190
20191            let c = 3;
20192        }"
20193        .unindent(),
20194        cx,
20195    )
20196    .await;
20197
20198    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20199}
20200
20201#[gpui::test]
20202async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20203    let (buffer_id, mut cx) = setup_indent_guides_editor(
20204        &"
20205        fn main() {
20206            let a = 1;
20207
20208            let c = 3;
20209
20210            if a == 3 {
20211                let b = 2;
20212            } else {
20213                let c = 3;
20214            }
20215        }"
20216        .unindent(),
20217        cx,
20218    )
20219    .await;
20220
20221    assert_indent_guides(
20222        0..11,
20223        vec![
20224            indent_guide(buffer_id, 1, 9, 0),
20225            indent_guide(buffer_id, 6, 6, 1),
20226            indent_guide(buffer_id, 8, 8, 1),
20227        ],
20228        None,
20229        &mut cx,
20230    );
20231}
20232
20233#[gpui::test]
20234async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20235    let (buffer_id, mut cx) = setup_indent_guides_editor(
20236        &"
20237        fn main() {
20238            let a = 1;
20239
20240            let c = 3;
20241
20242            if a == 3 {
20243                let b = 2;
20244            } else {
20245                let c = 3;
20246            }
20247        }"
20248        .unindent(),
20249        cx,
20250    )
20251    .await;
20252
20253    assert_indent_guides(
20254        1..11,
20255        vec![
20256            indent_guide(buffer_id, 1, 9, 0),
20257            indent_guide(buffer_id, 6, 6, 1),
20258            indent_guide(buffer_id, 8, 8, 1),
20259        ],
20260        None,
20261        &mut cx,
20262    );
20263}
20264
20265#[gpui::test]
20266async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20267    let (buffer_id, mut cx) = setup_indent_guides_editor(
20268        &"
20269        fn main() {
20270            let a = 1;
20271
20272            let c = 3;
20273
20274            if a == 3 {
20275                let b = 2;
20276            } else {
20277                let c = 3;
20278            }
20279        }"
20280        .unindent(),
20281        cx,
20282    )
20283    .await;
20284
20285    assert_indent_guides(
20286        1..10,
20287        vec![
20288            indent_guide(buffer_id, 1, 9, 0),
20289            indent_guide(buffer_id, 6, 6, 1),
20290            indent_guide(buffer_id, 8, 8, 1),
20291        ],
20292        None,
20293        &mut cx,
20294    );
20295}
20296
20297#[gpui::test]
20298async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20299    let (buffer_id, mut cx) = setup_indent_guides_editor(
20300        &"
20301        fn main() {
20302            if a {
20303                b(
20304                    c,
20305                    d,
20306                )
20307            } else {
20308                e(
20309                    f
20310                )
20311            }
20312        }"
20313        .unindent(),
20314        cx,
20315    )
20316    .await;
20317
20318    assert_indent_guides(
20319        0..11,
20320        vec![
20321            indent_guide(buffer_id, 1, 10, 0),
20322            indent_guide(buffer_id, 2, 5, 1),
20323            indent_guide(buffer_id, 7, 9, 1),
20324            indent_guide(buffer_id, 3, 4, 2),
20325            indent_guide(buffer_id, 8, 8, 2),
20326        ],
20327        None,
20328        &mut cx,
20329    );
20330
20331    cx.update_editor(|editor, window, cx| {
20332        editor.fold_at(MultiBufferRow(2), window, cx);
20333        assert_eq!(
20334            editor.display_text(cx),
20335            "
20336            fn main() {
20337                if a {
20338                    b(⋯
20339                    )
20340                } else {
20341                    e(
20342                        f
20343                    )
20344                }
20345            }"
20346            .unindent()
20347        );
20348    });
20349
20350    assert_indent_guides(
20351        0..11,
20352        vec![
20353            indent_guide(buffer_id, 1, 10, 0),
20354            indent_guide(buffer_id, 2, 5, 1),
20355            indent_guide(buffer_id, 7, 9, 1),
20356            indent_guide(buffer_id, 8, 8, 2),
20357        ],
20358        None,
20359        &mut cx,
20360    );
20361}
20362
20363#[gpui::test]
20364async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20365    let (buffer_id, mut cx) = setup_indent_guides_editor(
20366        &"
20367        block1
20368            block2
20369                block3
20370                    block4
20371            block2
20372        block1
20373        block1"
20374            .unindent(),
20375        cx,
20376    )
20377    .await;
20378
20379    assert_indent_guides(
20380        1..10,
20381        vec![
20382            indent_guide(buffer_id, 1, 4, 0),
20383            indent_guide(buffer_id, 2, 3, 1),
20384            indent_guide(buffer_id, 3, 3, 2),
20385        ],
20386        None,
20387        &mut cx,
20388    );
20389}
20390
20391#[gpui::test]
20392async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20393    let (buffer_id, mut cx) = setup_indent_guides_editor(
20394        &"
20395        block1
20396            block2
20397                block3
20398
20399        block1
20400        block1"
20401            .unindent(),
20402        cx,
20403    )
20404    .await;
20405
20406    assert_indent_guides(
20407        0..6,
20408        vec![
20409            indent_guide(buffer_id, 1, 2, 0),
20410            indent_guide(buffer_id, 2, 2, 1),
20411        ],
20412        None,
20413        &mut cx,
20414    );
20415}
20416
20417#[gpui::test]
20418async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20419    let (buffer_id, mut cx) = setup_indent_guides_editor(
20420        &"
20421        function component() {
20422        \treturn (
20423        \t\t\t
20424        \t\t<div>
20425        \t\t\t<abc></abc>
20426        \t\t</div>
20427        \t)
20428        }"
20429        .unindent(),
20430        cx,
20431    )
20432    .await;
20433
20434    assert_indent_guides(
20435        0..8,
20436        vec![
20437            indent_guide(buffer_id, 1, 6, 0),
20438            indent_guide(buffer_id, 2, 5, 1),
20439            indent_guide(buffer_id, 4, 4, 2),
20440        ],
20441        None,
20442        &mut cx,
20443    );
20444}
20445
20446#[gpui::test]
20447async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20448    let (buffer_id, mut cx) = setup_indent_guides_editor(
20449        &"
20450        function component() {
20451        \treturn (
20452        \t
20453        \t\t<div>
20454        \t\t\t<abc></abc>
20455        \t\t</div>
20456        \t)
20457        }"
20458        .unindent(),
20459        cx,
20460    )
20461    .await;
20462
20463    assert_indent_guides(
20464        0..8,
20465        vec![
20466            indent_guide(buffer_id, 1, 6, 0),
20467            indent_guide(buffer_id, 2, 5, 1),
20468            indent_guide(buffer_id, 4, 4, 2),
20469        ],
20470        None,
20471        &mut cx,
20472    );
20473}
20474
20475#[gpui::test]
20476async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20477    let (buffer_id, mut cx) = setup_indent_guides_editor(
20478        &"
20479        block1
20480
20481
20482
20483            block2
20484        "
20485        .unindent(),
20486        cx,
20487    )
20488    .await;
20489
20490    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20491}
20492
20493#[gpui::test]
20494async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20495    let (buffer_id, mut cx) = setup_indent_guides_editor(
20496        &"
20497        def a:
20498        \tb = 3
20499        \tif True:
20500        \t\tc = 4
20501        \t\td = 5
20502        \tprint(b)
20503        "
20504        .unindent(),
20505        cx,
20506    )
20507    .await;
20508
20509    assert_indent_guides(
20510        0..6,
20511        vec![
20512            indent_guide(buffer_id, 1, 5, 0),
20513            indent_guide(buffer_id, 3, 4, 1),
20514        ],
20515        None,
20516        &mut cx,
20517    );
20518}
20519
20520#[gpui::test]
20521async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20522    let (buffer_id, mut cx) = setup_indent_guides_editor(
20523        &"
20524    fn main() {
20525        let a = 1;
20526    }"
20527        .unindent(),
20528        cx,
20529    )
20530    .await;
20531
20532    cx.update_editor(|editor, window, cx| {
20533        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20534            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20535        });
20536    });
20537
20538    assert_indent_guides(
20539        0..3,
20540        vec![indent_guide(buffer_id, 1, 1, 0)],
20541        Some(vec![0]),
20542        &mut cx,
20543    );
20544}
20545
20546#[gpui::test]
20547async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20548    let (buffer_id, mut cx) = setup_indent_guides_editor(
20549        &"
20550    fn main() {
20551        if 1 == 2 {
20552            let a = 1;
20553        }
20554    }"
20555        .unindent(),
20556        cx,
20557    )
20558    .await;
20559
20560    cx.update_editor(|editor, window, cx| {
20561        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20562            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20563        });
20564    });
20565
20566    assert_indent_guides(
20567        0..4,
20568        vec![
20569            indent_guide(buffer_id, 1, 3, 0),
20570            indent_guide(buffer_id, 2, 2, 1),
20571        ],
20572        Some(vec![1]),
20573        &mut cx,
20574    );
20575
20576    cx.update_editor(|editor, window, cx| {
20577        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20578            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20579        });
20580    });
20581
20582    assert_indent_guides(
20583        0..4,
20584        vec![
20585            indent_guide(buffer_id, 1, 3, 0),
20586            indent_guide(buffer_id, 2, 2, 1),
20587        ],
20588        Some(vec![1]),
20589        &mut cx,
20590    );
20591
20592    cx.update_editor(|editor, window, cx| {
20593        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20594            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20595        });
20596    });
20597
20598    assert_indent_guides(
20599        0..4,
20600        vec![
20601            indent_guide(buffer_id, 1, 3, 0),
20602            indent_guide(buffer_id, 2, 2, 1),
20603        ],
20604        Some(vec![0]),
20605        &mut cx,
20606    );
20607}
20608
20609#[gpui::test]
20610async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
20611    let (buffer_id, mut cx) = setup_indent_guides_editor(
20612        &"
20613    fn main() {
20614        let a = 1;
20615
20616        let b = 2;
20617    }"
20618        .unindent(),
20619        cx,
20620    )
20621    .await;
20622
20623    cx.update_editor(|editor, window, cx| {
20624        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20625            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20626        });
20627    });
20628
20629    assert_indent_guides(
20630        0..5,
20631        vec![indent_guide(buffer_id, 1, 3, 0)],
20632        Some(vec![0]),
20633        &mut cx,
20634    );
20635}
20636
20637#[gpui::test]
20638async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
20639    let (buffer_id, mut cx) = setup_indent_guides_editor(
20640        &"
20641    def m:
20642        a = 1
20643        pass"
20644            .unindent(),
20645        cx,
20646    )
20647    .await;
20648
20649    cx.update_editor(|editor, window, cx| {
20650        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20651            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20652        });
20653    });
20654
20655    assert_indent_guides(
20656        0..3,
20657        vec![indent_guide(buffer_id, 1, 2, 0)],
20658        Some(vec![0]),
20659        &mut cx,
20660    );
20661}
20662
20663#[gpui::test]
20664async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
20665    init_test(cx, |_| {});
20666    let mut cx = EditorTestContext::new(cx).await;
20667    let text = indoc! {
20668        "
20669        impl A {
20670            fn b() {
20671                0;
20672                3;
20673                5;
20674                6;
20675                7;
20676            }
20677        }
20678        "
20679    };
20680    let base_text = indoc! {
20681        "
20682        impl A {
20683            fn b() {
20684                0;
20685                1;
20686                2;
20687                3;
20688                4;
20689            }
20690            fn c() {
20691                5;
20692                6;
20693                7;
20694            }
20695        }
20696        "
20697    };
20698
20699    cx.update_editor(|editor, window, cx| {
20700        editor.set_text(text, window, cx);
20701
20702        editor.buffer().update(cx, |multibuffer, cx| {
20703            let buffer = multibuffer.as_singleton().unwrap();
20704            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
20705
20706            multibuffer.set_all_diff_hunks_expanded(cx);
20707            multibuffer.add_diff(diff, cx);
20708
20709            buffer.read(cx).remote_id()
20710        })
20711    });
20712    cx.run_until_parked();
20713
20714    cx.assert_state_with_diff(
20715        indoc! { "
20716          impl A {
20717              fn b() {
20718                  0;
20719        -         1;
20720        -         2;
20721                  3;
20722        -         4;
20723        -     }
20724        -     fn c() {
20725                  5;
20726                  6;
20727                  7;
20728              }
20729          }
20730          ˇ"
20731        }
20732        .to_string(),
20733    );
20734
20735    let mut actual_guides = cx.update_editor(|editor, window, cx| {
20736        editor
20737            .snapshot(window, cx)
20738            .buffer_snapshot
20739            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
20740            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
20741            .collect::<Vec<_>>()
20742    });
20743    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
20744    assert_eq!(
20745        actual_guides,
20746        vec![
20747            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
20748            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
20749            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
20750        ]
20751    );
20752}
20753
20754#[gpui::test]
20755async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20756    init_test(cx, |_| {});
20757    let mut cx = EditorTestContext::new(cx).await;
20758
20759    let diff_base = r#"
20760        a
20761        b
20762        c
20763        "#
20764    .unindent();
20765
20766    cx.set_state(
20767        &r#"
20768        ˇA
20769        b
20770        C
20771        "#
20772        .unindent(),
20773    );
20774    cx.set_head_text(&diff_base);
20775    cx.update_editor(|editor, window, cx| {
20776        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20777    });
20778    executor.run_until_parked();
20779
20780    let both_hunks_expanded = r#"
20781        - a
20782        + ˇA
20783          b
20784        - c
20785        + C
20786        "#
20787    .unindent();
20788
20789    cx.assert_state_with_diff(both_hunks_expanded.clone());
20790
20791    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20792        let snapshot = editor.snapshot(window, cx);
20793        let hunks = editor
20794            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20795            .collect::<Vec<_>>();
20796        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20797        let buffer_id = hunks[0].buffer_id;
20798        hunks
20799            .into_iter()
20800            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20801            .collect::<Vec<_>>()
20802    });
20803    assert_eq!(hunk_ranges.len(), 2);
20804
20805    cx.update_editor(|editor, _, cx| {
20806        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20807    });
20808    executor.run_until_parked();
20809
20810    let second_hunk_expanded = r#"
20811          ˇA
20812          b
20813        - c
20814        + C
20815        "#
20816    .unindent();
20817
20818    cx.assert_state_with_diff(second_hunk_expanded);
20819
20820    cx.update_editor(|editor, _, cx| {
20821        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20822    });
20823    executor.run_until_parked();
20824
20825    cx.assert_state_with_diff(both_hunks_expanded.clone());
20826
20827    cx.update_editor(|editor, _, cx| {
20828        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20829    });
20830    executor.run_until_parked();
20831
20832    let first_hunk_expanded = r#"
20833        - a
20834        + ˇA
20835          b
20836          C
20837        "#
20838    .unindent();
20839
20840    cx.assert_state_with_diff(first_hunk_expanded);
20841
20842    cx.update_editor(|editor, _, cx| {
20843        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20844    });
20845    executor.run_until_parked();
20846
20847    cx.assert_state_with_diff(both_hunks_expanded);
20848
20849    cx.set_state(
20850        &r#"
20851        ˇA
20852        b
20853        "#
20854        .unindent(),
20855    );
20856    cx.run_until_parked();
20857
20858    // TODO this cursor position seems bad
20859    cx.assert_state_with_diff(
20860        r#"
20861        - ˇa
20862        + A
20863          b
20864        "#
20865        .unindent(),
20866    );
20867
20868    cx.update_editor(|editor, window, cx| {
20869        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20870    });
20871
20872    cx.assert_state_with_diff(
20873        r#"
20874            - ˇa
20875            + A
20876              b
20877            - c
20878            "#
20879        .unindent(),
20880    );
20881
20882    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20883        let snapshot = editor.snapshot(window, cx);
20884        let hunks = editor
20885            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20886            .collect::<Vec<_>>();
20887        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20888        let buffer_id = hunks[0].buffer_id;
20889        hunks
20890            .into_iter()
20891            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20892            .collect::<Vec<_>>()
20893    });
20894    assert_eq!(hunk_ranges.len(), 2);
20895
20896    cx.update_editor(|editor, _, cx| {
20897        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20898    });
20899    executor.run_until_parked();
20900
20901    cx.assert_state_with_diff(
20902        r#"
20903        - ˇa
20904        + A
20905          b
20906        "#
20907        .unindent(),
20908    );
20909}
20910
20911#[gpui::test]
20912async fn test_toggle_deletion_hunk_at_start_of_file(
20913    executor: BackgroundExecutor,
20914    cx: &mut TestAppContext,
20915) {
20916    init_test(cx, |_| {});
20917    let mut cx = EditorTestContext::new(cx).await;
20918
20919    let diff_base = r#"
20920        a
20921        b
20922        c
20923        "#
20924    .unindent();
20925
20926    cx.set_state(
20927        &r#"
20928        ˇb
20929        c
20930        "#
20931        .unindent(),
20932    );
20933    cx.set_head_text(&diff_base);
20934    cx.update_editor(|editor, window, cx| {
20935        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20936    });
20937    executor.run_until_parked();
20938
20939    let hunk_expanded = r#"
20940        - a
20941          ˇb
20942          c
20943        "#
20944    .unindent();
20945
20946    cx.assert_state_with_diff(hunk_expanded.clone());
20947
20948    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20949        let snapshot = editor.snapshot(window, cx);
20950        let hunks = editor
20951            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20952            .collect::<Vec<_>>();
20953        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20954        let buffer_id = hunks[0].buffer_id;
20955        hunks
20956            .into_iter()
20957            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20958            .collect::<Vec<_>>()
20959    });
20960    assert_eq!(hunk_ranges.len(), 1);
20961
20962    cx.update_editor(|editor, _, cx| {
20963        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20964    });
20965    executor.run_until_parked();
20966
20967    let hunk_collapsed = r#"
20968          ˇb
20969          c
20970        "#
20971    .unindent();
20972
20973    cx.assert_state_with_diff(hunk_collapsed);
20974
20975    cx.update_editor(|editor, _, cx| {
20976        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20977    });
20978    executor.run_until_parked();
20979
20980    cx.assert_state_with_diff(hunk_expanded);
20981}
20982
20983#[gpui::test]
20984async fn test_display_diff_hunks(cx: &mut TestAppContext) {
20985    init_test(cx, |_| {});
20986
20987    let fs = FakeFs::new(cx.executor());
20988    fs.insert_tree(
20989        path!("/test"),
20990        json!({
20991            ".git": {},
20992            "file-1": "ONE\n",
20993            "file-2": "TWO\n",
20994            "file-3": "THREE\n",
20995        }),
20996    )
20997    .await;
20998
20999    fs.set_head_for_repo(
21000        path!("/test/.git").as_ref(),
21001        &[
21002            ("file-1", "one\n".into()),
21003            ("file-2", "two\n".into()),
21004            ("file-3", "three\n".into()),
21005        ],
21006        "deadbeef",
21007    );
21008
21009    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
21010    let mut buffers = vec![];
21011    for i in 1..=3 {
21012        let buffer = project
21013            .update(cx, |project, cx| {
21014                let path = format!(path!("/test/file-{}"), i);
21015                project.open_local_buffer(path, cx)
21016            })
21017            .await
21018            .unwrap();
21019        buffers.push(buffer);
21020    }
21021
21022    let multibuffer = cx.new(|cx| {
21023        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
21024        multibuffer.set_all_diff_hunks_expanded(cx);
21025        for buffer in &buffers {
21026            let snapshot = buffer.read(cx).snapshot();
21027            multibuffer.set_excerpts_for_path(
21028                PathKey::namespaced(
21029                    0,
21030                    buffer.read(cx).file().unwrap().path().as_unix_str().into(),
21031                ),
21032                buffer.clone(),
21033                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
21034                2,
21035                cx,
21036            );
21037        }
21038        multibuffer
21039    });
21040
21041    let editor = cx.add_window(|window, cx| {
21042        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
21043    });
21044    cx.run_until_parked();
21045
21046    let snapshot = editor
21047        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21048        .unwrap();
21049    let hunks = snapshot
21050        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
21051        .map(|hunk| match hunk {
21052            DisplayDiffHunk::Unfolded {
21053                display_row_range, ..
21054            } => display_row_range,
21055            DisplayDiffHunk::Folded { .. } => unreachable!(),
21056        })
21057        .collect::<Vec<_>>();
21058    assert_eq!(
21059        hunks,
21060        [
21061            DisplayRow(2)..DisplayRow(4),
21062            DisplayRow(7)..DisplayRow(9),
21063            DisplayRow(12)..DisplayRow(14),
21064        ]
21065    );
21066}
21067
21068#[gpui::test]
21069async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
21070    init_test(cx, |_| {});
21071
21072    let mut cx = EditorTestContext::new(cx).await;
21073    cx.set_head_text(indoc! { "
21074        one
21075        two
21076        three
21077        four
21078        five
21079        "
21080    });
21081    cx.set_index_text(indoc! { "
21082        one
21083        two
21084        three
21085        four
21086        five
21087        "
21088    });
21089    cx.set_state(indoc! {"
21090        one
21091        TWO
21092        ˇTHREE
21093        FOUR
21094        five
21095    "});
21096    cx.run_until_parked();
21097    cx.update_editor(|editor, window, cx| {
21098        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21099    });
21100    cx.run_until_parked();
21101    cx.assert_index_text(Some(indoc! {"
21102        one
21103        TWO
21104        THREE
21105        FOUR
21106        five
21107    "}));
21108    cx.set_state(indoc! { "
21109        one
21110        TWO
21111        ˇTHREE-HUNDRED
21112        FOUR
21113        five
21114    "});
21115    cx.run_until_parked();
21116    cx.update_editor(|editor, window, cx| {
21117        let snapshot = editor.snapshot(window, cx);
21118        let hunks = editor
21119            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
21120            .collect::<Vec<_>>();
21121        assert_eq!(hunks.len(), 1);
21122        assert_eq!(
21123            hunks[0].status(),
21124            DiffHunkStatus {
21125                kind: DiffHunkStatusKind::Modified,
21126                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21127            }
21128        );
21129
21130        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21131    });
21132    cx.run_until_parked();
21133    cx.assert_index_text(Some(indoc! {"
21134        one
21135        TWO
21136        THREE-HUNDRED
21137        FOUR
21138        five
21139    "}));
21140}
21141
21142#[gpui::test]
21143fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21144    init_test(cx, |_| {});
21145
21146    let editor = cx.add_window(|window, cx| {
21147        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21148        build_editor(buffer, window, cx)
21149    });
21150
21151    let render_args = Arc::new(Mutex::new(None));
21152    let snapshot = editor
21153        .update(cx, |editor, window, cx| {
21154            let snapshot = editor.buffer().read(cx).snapshot(cx);
21155            let range =
21156                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21157
21158            struct RenderArgs {
21159                row: MultiBufferRow,
21160                folded: bool,
21161                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21162            }
21163
21164            let crease = Crease::inline(
21165                range,
21166                FoldPlaceholder::test(),
21167                {
21168                    let toggle_callback = render_args.clone();
21169                    move |row, folded, callback, _window, _cx| {
21170                        *toggle_callback.lock() = Some(RenderArgs {
21171                            row,
21172                            folded,
21173                            callback,
21174                        });
21175                        div()
21176                    }
21177                },
21178                |_row, _folded, _window, _cx| div(),
21179            );
21180
21181            editor.insert_creases(Some(crease), cx);
21182            let snapshot = editor.snapshot(window, cx);
21183            let _div =
21184                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21185            snapshot
21186        })
21187        .unwrap();
21188
21189    let render_args = render_args.lock().take().unwrap();
21190    assert_eq!(render_args.row, MultiBufferRow(1));
21191    assert!(!render_args.folded);
21192    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21193
21194    cx.update_window(*editor, |_, window, cx| {
21195        (render_args.callback)(true, window, cx)
21196    })
21197    .unwrap();
21198    let snapshot = editor
21199        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21200        .unwrap();
21201    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21202
21203    cx.update_window(*editor, |_, window, cx| {
21204        (render_args.callback)(false, window, cx)
21205    })
21206    .unwrap();
21207    let snapshot = editor
21208        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21209        .unwrap();
21210    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21211}
21212
21213#[gpui::test]
21214async fn test_input_text(cx: &mut TestAppContext) {
21215    init_test(cx, |_| {});
21216    let mut cx = EditorTestContext::new(cx).await;
21217
21218    cx.set_state(
21219        &r#"ˇone
21220        two
21221
21222        three
21223        fourˇ
21224        five
21225
21226        siˇx"#
21227            .unindent(),
21228    );
21229
21230    cx.dispatch_action(HandleInput(String::new()));
21231    cx.assert_editor_state(
21232        &r#"ˇone
21233        two
21234
21235        three
21236        fourˇ
21237        five
21238
21239        siˇx"#
21240            .unindent(),
21241    );
21242
21243    cx.dispatch_action(HandleInput("AAAA".to_string()));
21244    cx.assert_editor_state(
21245        &r#"AAAAˇone
21246        two
21247
21248        three
21249        fourAAAAˇ
21250        five
21251
21252        siAAAAˇx"#
21253            .unindent(),
21254    );
21255}
21256
21257#[gpui::test]
21258async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
21259    init_test(cx, |_| {});
21260
21261    let mut cx = EditorTestContext::new(cx).await;
21262    cx.set_state(
21263        r#"let foo = 1;
21264let foo = 2;
21265let foo = 3;
21266let fooˇ = 4;
21267let foo = 5;
21268let foo = 6;
21269let foo = 7;
21270let foo = 8;
21271let foo = 9;
21272let foo = 10;
21273let foo = 11;
21274let foo = 12;
21275let foo = 13;
21276let foo = 14;
21277let foo = 15;"#,
21278    );
21279
21280    cx.update_editor(|e, window, cx| {
21281        assert_eq!(
21282            e.next_scroll_position,
21283            NextScrollCursorCenterTopBottom::Center,
21284            "Default next scroll direction is center",
21285        );
21286
21287        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21288        assert_eq!(
21289            e.next_scroll_position,
21290            NextScrollCursorCenterTopBottom::Top,
21291            "After center, next scroll direction should be top",
21292        );
21293
21294        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21295        assert_eq!(
21296            e.next_scroll_position,
21297            NextScrollCursorCenterTopBottom::Bottom,
21298            "After top, next scroll direction should be bottom",
21299        );
21300
21301        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21302        assert_eq!(
21303            e.next_scroll_position,
21304            NextScrollCursorCenterTopBottom::Center,
21305            "After bottom, scrolling should start over",
21306        );
21307
21308        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21309        assert_eq!(
21310            e.next_scroll_position,
21311            NextScrollCursorCenterTopBottom::Top,
21312            "Scrolling continues if retriggered fast enough"
21313        );
21314    });
21315
21316    cx.executor()
21317        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
21318    cx.executor().run_until_parked();
21319    cx.update_editor(|e, _, _| {
21320        assert_eq!(
21321            e.next_scroll_position,
21322            NextScrollCursorCenterTopBottom::Center,
21323            "If scrolling is not triggered fast enough, it should reset"
21324        );
21325    });
21326}
21327
21328#[gpui::test]
21329async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
21330    init_test(cx, |_| {});
21331    let mut cx = EditorLspTestContext::new_rust(
21332        lsp::ServerCapabilities {
21333            definition_provider: Some(lsp::OneOf::Left(true)),
21334            references_provider: Some(lsp::OneOf::Left(true)),
21335            ..lsp::ServerCapabilities::default()
21336        },
21337        cx,
21338    )
21339    .await;
21340
21341    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21342        let go_to_definition = cx
21343            .lsp
21344            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21345                move |params, _| async move {
21346                    if empty_go_to_definition {
21347                        Ok(None)
21348                    } else {
21349                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21350                            uri: params.text_document_position_params.text_document.uri,
21351                            range: lsp::Range::new(
21352                                lsp::Position::new(4, 3),
21353                                lsp::Position::new(4, 6),
21354                            ),
21355                        })))
21356                    }
21357                },
21358            );
21359        let references = cx
21360            .lsp
21361            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21362                Ok(Some(vec![lsp::Location {
21363                    uri: params.text_document_position.text_document.uri,
21364                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21365                }]))
21366            });
21367        (go_to_definition, references)
21368    };
21369
21370    cx.set_state(
21371        &r#"fn one() {
21372            let mut a = ˇtwo();
21373        }
21374
21375        fn two() {}"#
21376            .unindent(),
21377    );
21378    set_up_lsp_handlers(false, &mut cx);
21379    let navigated = cx
21380        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21381        .await
21382        .expect("Failed to navigate to definition");
21383    assert_eq!(
21384        navigated,
21385        Navigated::Yes,
21386        "Should have navigated to definition from the GetDefinition response"
21387    );
21388    cx.assert_editor_state(
21389        &r#"fn one() {
21390            let mut a = two();
21391        }
21392
21393        fn «twoˇ»() {}"#
21394            .unindent(),
21395    );
21396
21397    let editors = cx.update_workspace(|workspace, _, cx| {
21398        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21399    });
21400    cx.update_editor(|_, _, test_editor_cx| {
21401        assert_eq!(
21402            editors.len(),
21403            1,
21404            "Initially, only one, test, editor should be open in the workspace"
21405        );
21406        assert_eq!(
21407            test_editor_cx.entity(),
21408            editors.last().expect("Asserted len is 1").clone()
21409        );
21410    });
21411
21412    set_up_lsp_handlers(true, &mut cx);
21413    let navigated = cx
21414        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21415        .await
21416        .expect("Failed to navigate to lookup references");
21417    assert_eq!(
21418        navigated,
21419        Navigated::Yes,
21420        "Should have navigated to references as a fallback after empty GoToDefinition response"
21421    );
21422    // We should not change the selections in the existing file,
21423    // if opening another milti buffer with the references
21424    cx.assert_editor_state(
21425        &r#"fn one() {
21426            let mut a = two();
21427        }
21428
21429        fn «twoˇ»() {}"#
21430            .unindent(),
21431    );
21432    let editors = cx.update_workspace(|workspace, _, cx| {
21433        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21434    });
21435    cx.update_editor(|_, _, test_editor_cx| {
21436        assert_eq!(
21437            editors.len(),
21438            2,
21439            "After falling back to references search, we open a new editor with the results"
21440        );
21441        let references_fallback_text = editors
21442            .into_iter()
21443            .find(|new_editor| *new_editor != test_editor_cx.entity())
21444            .expect("Should have one non-test editor now")
21445            .read(test_editor_cx)
21446            .text(test_editor_cx);
21447        assert_eq!(
21448            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21449            "Should use the range from the references response and not the GoToDefinition one"
21450        );
21451    });
21452}
21453
21454#[gpui::test]
21455async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21456    init_test(cx, |_| {});
21457    cx.update(|cx| {
21458        let mut editor_settings = EditorSettings::get_global(cx).clone();
21459        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21460        EditorSettings::override_global(editor_settings, cx);
21461    });
21462    let mut cx = EditorLspTestContext::new_rust(
21463        lsp::ServerCapabilities {
21464            definition_provider: Some(lsp::OneOf::Left(true)),
21465            references_provider: Some(lsp::OneOf::Left(true)),
21466            ..lsp::ServerCapabilities::default()
21467        },
21468        cx,
21469    )
21470    .await;
21471    let original_state = r#"fn one() {
21472        let mut a = ˇtwo();
21473    }
21474
21475    fn two() {}"#
21476        .unindent();
21477    cx.set_state(&original_state);
21478
21479    let mut go_to_definition = cx
21480        .lsp
21481        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21482            move |_, _| async move { Ok(None) },
21483        );
21484    let _references = cx
21485        .lsp
21486        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21487            panic!("Should not call for references with no go to definition fallback")
21488        });
21489
21490    let navigated = cx
21491        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21492        .await
21493        .expect("Failed to navigate to lookup references");
21494    go_to_definition
21495        .next()
21496        .await
21497        .expect("Should have called the go_to_definition handler");
21498
21499    assert_eq!(
21500        navigated,
21501        Navigated::No,
21502        "Should have navigated to references as a fallback after empty GoToDefinition response"
21503    );
21504    cx.assert_editor_state(&original_state);
21505    let editors = cx.update_workspace(|workspace, _, cx| {
21506        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21507    });
21508    cx.update_editor(|_, _, _| {
21509        assert_eq!(
21510            editors.len(),
21511            1,
21512            "After unsuccessful fallback, no other editor should have been opened"
21513        );
21514    });
21515}
21516
21517#[gpui::test]
21518async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
21519    init_test(cx, |_| {});
21520    let mut cx = EditorLspTestContext::new_rust(
21521        lsp::ServerCapabilities {
21522            references_provider: Some(lsp::OneOf::Left(true)),
21523            ..lsp::ServerCapabilities::default()
21524        },
21525        cx,
21526    )
21527    .await;
21528
21529    cx.set_state(
21530        &r#"
21531        fn one() {
21532            let mut a = two();
21533        }
21534
21535        fn ˇtwo() {}"#
21536            .unindent(),
21537    );
21538    cx.lsp
21539        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21540            Ok(Some(vec![
21541                lsp::Location {
21542                    uri: params.text_document_position.text_document.uri.clone(),
21543                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21544                },
21545                lsp::Location {
21546                    uri: params.text_document_position.text_document.uri,
21547                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
21548                },
21549            ]))
21550        });
21551    let navigated = cx
21552        .update_editor(|editor, window, cx| {
21553            editor.find_all_references(&FindAllReferences, window, cx)
21554        })
21555        .unwrap()
21556        .await
21557        .expect("Failed to navigate to references");
21558    assert_eq!(
21559        navigated,
21560        Navigated::Yes,
21561        "Should have navigated to references from the FindAllReferences response"
21562    );
21563    cx.assert_editor_state(
21564        &r#"fn one() {
21565            let mut a = two();
21566        }
21567
21568        fn ˇtwo() {}"#
21569            .unindent(),
21570    );
21571
21572    let editors = cx.update_workspace(|workspace, _, cx| {
21573        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21574    });
21575    cx.update_editor(|_, _, _| {
21576        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
21577    });
21578
21579    cx.set_state(
21580        &r#"fn one() {
21581            let mut a = ˇtwo();
21582        }
21583
21584        fn two() {}"#
21585            .unindent(),
21586    );
21587    let navigated = cx
21588        .update_editor(|editor, window, cx| {
21589            editor.find_all_references(&FindAllReferences, window, cx)
21590        })
21591        .unwrap()
21592        .await
21593        .expect("Failed to navigate to references");
21594    assert_eq!(
21595        navigated,
21596        Navigated::Yes,
21597        "Should have navigated to references from the FindAllReferences response"
21598    );
21599    cx.assert_editor_state(
21600        &r#"fn one() {
21601            let mut a = ˇtwo();
21602        }
21603
21604        fn two() {}"#
21605            .unindent(),
21606    );
21607    let editors = cx.update_workspace(|workspace, _, cx| {
21608        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21609    });
21610    cx.update_editor(|_, _, _| {
21611        assert_eq!(
21612            editors.len(),
21613            2,
21614            "should have re-used the previous multibuffer"
21615        );
21616    });
21617
21618    cx.set_state(
21619        &r#"fn one() {
21620            let mut a = ˇtwo();
21621        }
21622        fn three() {}
21623        fn two() {}"#
21624            .unindent(),
21625    );
21626    cx.lsp
21627        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21628            Ok(Some(vec![
21629                lsp::Location {
21630                    uri: params.text_document_position.text_document.uri.clone(),
21631                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21632                },
21633                lsp::Location {
21634                    uri: params.text_document_position.text_document.uri,
21635                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
21636                },
21637            ]))
21638        });
21639    let navigated = cx
21640        .update_editor(|editor, window, cx| {
21641            editor.find_all_references(&FindAllReferences, window, cx)
21642        })
21643        .unwrap()
21644        .await
21645        .expect("Failed to navigate to references");
21646    assert_eq!(
21647        navigated,
21648        Navigated::Yes,
21649        "Should have navigated to references from the FindAllReferences response"
21650    );
21651    cx.assert_editor_state(
21652        &r#"fn one() {
21653                let mut a = ˇtwo();
21654            }
21655            fn three() {}
21656            fn two() {}"#
21657            .unindent(),
21658    );
21659    let editors = cx.update_workspace(|workspace, _, cx| {
21660        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21661    });
21662    cx.update_editor(|_, _, _| {
21663        assert_eq!(
21664            editors.len(),
21665            3,
21666            "should have used a new multibuffer as offsets changed"
21667        );
21668    });
21669}
21670#[gpui::test]
21671async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
21672    init_test(cx, |_| {});
21673
21674    let language = Arc::new(Language::new(
21675        LanguageConfig::default(),
21676        Some(tree_sitter_rust::LANGUAGE.into()),
21677    ));
21678
21679    let text = r#"
21680        #[cfg(test)]
21681        mod tests() {
21682            #[test]
21683            fn runnable_1() {
21684                let a = 1;
21685            }
21686
21687            #[test]
21688            fn runnable_2() {
21689                let a = 1;
21690                let b = 2;
21691            }
21692        }
21693    "#
21694    .unindent();
21695
21696    let fs = FakeFs::new(cx.executor());
21697    fs.insert_file("/file.rs", Default::default()).await;
21698
21699    let project = Project::test(fs, ["/a".as_ref()], cx).await;
21700    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21701    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21702    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
21703    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
21704
21705    let editor = cx.new_window_entity(|window, cx| {
21706        Editor::new(
21707            EditorMode::full(),
21708            multi_buffer,
21709            Some(project.clone()),
21710            window,
21711            cx,
21712        )
21713    });
21714
21715    editor.update_in(cx, |editor, window, cx| {
21716        let snapshot = editor.buffer().read(cx).snapshot(cx);
21717        editor.tasks.insert(
21718            (buffer.read(cx).remote_id(), 3),
21719            RunnableTasks {
21720                templates: vec![],
21721                offset: snapshot.anchor_before(43),
21722                column: 0,
21723                extra_variables: HashMap::default(),
21724                context_range: BufferOffset(43)..BufferOffset(85),
21725            },
21726        );
21727        editor.tasks.insert(
21728            (buffer.read(cx).remote_id(), 8),
21729            RunnableTasks {
21730                templates: vec![],
21731                offset: snapshot.anchor_before(86),
21732                column: 0,
21733                extra_variables: HashMap::default(),
21734                context_range: BufferOffset(86)..BufferOffset(191),
21735            },
21736        );
21737
21738        // Test finding task when cursor is inside function body
21739        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21740            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
21741        });
21742        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21743        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
21744
21745        // Test finding task when cursor is on function name
21746        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21747            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
21748        });
21749        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21750        assert_eq!(row, 8, "Should find task when cursor is on function name");
21751    });
21752}
21753
21754#[gpui::test]
21755async fn test_folding_buffers(cx: &mut TestAppContext) {
21756    init_test(cx, |_| {});
21757
21758    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21759    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
21760    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
21761
21762    let fs = FakeFs::new(cx.executor());
21763    fs.insert_tree(
21764        path!("/a"),
21765        json!({
21766            "first.rs": sample_text_1,
21767            "second.rs": sample_text_2,
21768            "third.rs": sample_text_3,
21769        }),
21770    )
21771    .await;
21772    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21773    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21774    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21775    let worktree = project.update(cx, |project, cx| {
21776        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21777        assert_eq!(worktrees.len(), 1);
21778        worktrees.pop().unwrap()
21779    });
21780    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21781
21782    let buffer_1 = project
21783        .update(cx, |project, cx| {
21784            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
21785        })
21786        .await
21787        .unwrap();
21788    let buffer_2 = project
21789        .update(cx, |project, cx| {
21790            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
21791        })
21792        .await
21793        .unwrap();
21794    let buffer_3 = project
21795        .update(cx, |project, cx| {
21796            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
21797        })
21798        .await
21799        .unwrap();
21800
21801    let multi_buffer = cx.new(|cx| {
21802        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21803        multi_buffer.push_excerpts(
21804            buffer_1.clone(),
21805            [
21806                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21807                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21808                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21809            ],
21810            cx,
21811        );
21812        multi_buffer.push_excerpts(
21813            buffer_2.clone(),
21814            [
21815                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21816                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21817                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21818            ],
21819            cx,
21820        );
21821        multi_buffer.push_excerpts(
21822            buffer_3.clone(),
21823            [
21824                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21825                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21826                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21827            ],
21828            cx,
21829        );
21830        multi_buffer
21831    });
21832    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21833        Editor::new(
21834            EditorMode::full(),
21835            multi_buffer.clone(),
21836            Some(project.clone()),
21837            window,
21838            cx,
21839        )
21840    });
21841
21842    assert_eq!(
21843        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21844        "\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",
21845    );
21846
21847    multi_buffer_editor.update(cx, |editor, cx| {
21848        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21849    });
21850    assert_eq!(
21851        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21852        "\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",
21853        "After folding the first buffer, its text should not be displayed"
21854    );
21855
21856    multi_buffer_editor.update(cx, |editor, cx| {
21857        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21858    });
21859    assert_eq!(
21860        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21861        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
21862        "After folding the second buffer, its text should not be displayed"
21863    );
21864
21865    multi_buffer_editor.update(cx, |editor, cx| {
21866        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21867    });
21868    assert_eq!(
21869        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21870        "\n\n\n\n\n",
21871        "After folding the third buffer, its text should not be displayed"
21872    );
21873
21874    // Emulate selection inside the fold logic, that should work
21875    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21876        editor
21877            .snapshot(window, cx)
21878            .next_line_boundary(Point::new(0, 4));
21879    });
21880
21881    multi_buffer_editor.update(cx, |editor, cx| {
21882        editor.unfold_buffer(buffer_2.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\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21887        "After unfolding the second buffer, its text should be displayed"
21888    );
21889
21890    // Typing inside of buffer 1 causes that buffer to be unfolded.
21891    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21892        assert_eq!(
21893            multi_buffer
21894                .read(cx)
21895                .snapshot(cx)
21896                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
21897                .collect::<String>(),
21898            "bbbb"
21899        );
21900        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21901            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
21902        });
21903        editor.handle_input("B", window, cx);
21904    });
21905
21906    assert_eq!(
21907        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21908        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21909        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
21910    );
21911
21912    multi_buffer_editor.update(cx, |editor, cx| {
21913        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21914    });
21915    assert_eq!(
21916        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21917        "\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",
21918        "After unfolding the all buffers, all original text should be displayed"
21919    );
21920}
21921
21922#[gpui::test]
21923async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
21924    init_test(cx, |_| {});
21925
21926    let sample_text_1 = "1111\n2222\n3333".to_string();
21927    let sample_text_2 = "4444\n5555\n6666".to_string();
21928    let sample_text_3 = "7777\n8888\n9999".to_string();
21929
21930    let fs = FakeFs::new(cx.executor());
21931    fs.insert_tree(
21932        path!("/a"),
21933        json!({
21934            "first.rs": sample_text_1,
21935            "second.rs": sample_text_2,
21936            "third.rs": sample_text_3,
21937        }),
21938    )
21939    .await;
21940    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21941    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21942    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21943    let worktree = project.update(cx, |project, cx| {
21944        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21945        assert_eq!(worktrees.len(), 1);
21946        worktrees.pop().unwrap()
21947    });
21948    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21949
21950    let buffer_1 = project
21951        .update(cx, |project, cx| {
21952            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
21953        })
21954        .await
21955        .unwrap();
21956    let buffer_2 = project
21957        .update(cx, |project, cx| {
21958            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
21959        })
21960        .await
21961        .unwrap();
21962    let buffer_3 = project
21963        .update(cx, |project, cx| {
21964            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
21965        })
21966        .await
21967        .unwrap();
21968
21969    let multi_buffer = cx.new(|cx| {
21970        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21971        multi_buffer.push_excerpts(
21972            buffer_1.clone(),
21973            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21974            cx,
21975        );
21976        multi_buffer.push_excerpts(
21977            buffer_2.clone(),
21978            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21979            cx,
21980        );
21981        multi_buffer.push_excerpts(
21982            buffer_3.clone(),
21983            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21984            cx,
21985        );
21986        multi_buffer
21987    });
21988
21989    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21990        Editor::new(
21991            EditorMode::full(),
21992            multi_buffer,
21993            Some(project.clone()),
21994            window,
21995            cx,
21996        )
21997    });
21998
21999    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
22000    assert_eq!(
22001        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22002        full_text,
22003    );
22004
22005    multi_buffer_editor.update(cx, |editor, cx| {
22006        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22007    });
22008    assert_eq!(
22009        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22010        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
22011        "After folding the first buffer, its text should not be displayed"
22012    );
22013
22014    multi_buffer_editor.update(cx, |editor, cx| {
22015        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22016    });
22017
22018    assert_eq!(
22019        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22020        "\n\n\n\n\n\n7777\n8888\n9999",
22021        "After folding the second buffer, its text should not be displayed"
22022    );
22023
22024    multi_buffer_editor.update(cx, |editor, cx| {
22025        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22026    });
22027    assert_eq!(
22028        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22029        "\n\n\n\n\n",
22030        "After folding the third buffer, its text should not be displayed"
22031    );
22032
22033    multi_buffer_editor.update(cx, |editor, cx| {
22034        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22035    });
22036    assert_eq!(
22037        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22038        "\n\n\n\n4444\n5555\n6666\n\n",
22039        "After unfolding the second buffer, its text should be displayed"
22040    );
22041
22042    multi_buffer_editor.update(cx, |editor, cx| {
22043        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
22044    });
22045    assert_eq!(
22046        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22047        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
22048        "After unfolding the first buffer, its text should be displayed"
22049    );
22050
22051    multi_buffer_editor.update(cx, |editor, cx| {
22052        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22053    });
22054    assert_eq!(
22055        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22056        full_text,
22057        "After unfolding all buffers, all original text should be displayed"
22058    );
22059}
22060
22061#[gpui::test]
22062async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
22063    init_test(cx, |_| {});
22064
22065    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22066
22067    let fs = FakeFs::new(cx.executor());
22068    fs.insert_tree(
22069        path!("/a"),
22070        json!({
22071            "main.rs": sample_text,
22072        }),
22073    )
22074    .await;
22075    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22076    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22077    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22078    let worktree = project.update(cx, |project, cx| {
22079        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22080        assert_eq!(worktrees.len(), 1);
22081        worktrees.pop().unwrap()
22082    });
22083    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22084
22085    let buffer_1 = project
22086        .update(cx, |project, cx| {
22087            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22088        })
22089        .await
22090        .unwrap();
22091
22092    let multi_buffer = cx.new(|cx| {
22093        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22094        multi_buffer.push_excerpts(
22095            buffer_1.clone(),
22096            [ExcerptRange::new(
22097                Point::new(0, 0)
22098                    ..Point::new(
22099                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
22100                        0,
22101                    ),
22102            )],
22103            cx,
22104        );
22105        multi_buffer
22106    });
22107    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22108        Editor::new(
22109            EditorMode::full(),
22110            multi_buffer,
22111            Some(project.clone()),
22112            window,
22113            cx,
22114        )
22115    });
22116
22117    let selection_range = Point::new(1, 0)..Point::new(2, 0);
22118    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22119        enum TestHighlight {}
22120        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22121        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
22122        editor.highlight_text::<TestHighlight>(
22123            vec![highlight_range.clone()],
22124            HighlightStyle::color(Hsla::green()),
22125            cx,
22126        );
22127        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22128            s.select_ranges(Some(highlight_range))
22129        });
22130    });
22131
22132    let full_text = format!("\n\n{sample_text}");
22133    assert_eq!(
22134        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22135        full_text,
22136    );
22137}
22138
22139#[gpui::test]
22140async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22141    init_test(cx, |_| {});
22142    cx.update(|cx| {
22143        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22144            "keymaps/default-linux.json",
22145            cx,
22146        )
22147        .unwrap();
22148        cx.bind_keys(default_key_bindings);
22149    });
22150
22151    let (editor, cx) = cx.add_window_view(|window, cx| {
22152        let multi_buffer = MultiBuffer::build_multi(
22153            [
22154                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22155                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22156                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22157                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22158            ],
22159            cx,
22160        );
22161        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22162
22163        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22164        // fold all but the second buffer, so that we test navigating between two
22165        // adjacent folded buffers, as well as folded buffers at the start and
22166        // end the multibuffer
22167        editor.fold_buffer(buffer_ids[0], cx);
22168        editor.fold_buffer(buffer_ids[2], cx);
22169        editor.fold_buffer(buffer_ids[3], cx);
22170
22171        editor
22172    });
22173    cx.simulate_resize(size(px(1000.), px(1000.)));
22174
22175    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22176    cx.assert_excerpts_with_selections(indoc! {"
22177        [EXCERPT]
22178        ˇ[FOLDED]
22179        [EXCERPT]
22180        a1
22181        b1
22182        [EXCERPT]
22183        [FOLDED]
22184        [EXCERPT]
22185        [FOLDED]
22186        "
22187    });
22188    cx.simulate_keystroke("down");
22189    cx.assert_excerpts_with_selections(indoc! {"
22190        [EXCERPT]
22191        [FOLDED]
22192        [EXCERPT]
22193        ˇa1
22194        b1
22195        [EXCERPT]
22196        [FOLDED]
22197        [EXCERPT]
22198        [FOLDED]
22199        "
22200    });
22201    cx.simulate_keystroke("down");
22202    cx.assert_excerpts_with_selections(indoc! {"
22203        [EXCERPT]
22204        [FOLDED]
22205        [EXCERPT]
22206        a1
22207        ˇb1
22208        [EXCERPT]
22209        [FOLDED]
22210        [EXCERPT]
22211        [FOLDED]
22212        "
22213    });
22214    cx.simulate_keystroke("down");
22215    cx.assert_excerpts_with_selections(indoc! {"
22216        [EXCERPT]
22217        [FOLDED]
22218        [EXCERPT]
22219        a1
22220        b1
22221        ˇ[EXCERPT]
22222        [FOLDED]
22223        [EXCERPT]
22224        [FOLDED]
22225        "
22226    });
22227    cx.simulate_keystroke("down");
22228    cx.assert_excerpts_with_selections(indoc! {"
22229        [EXCERPT]
22230        [FOLDED]
22231        [EXCERPT]
22232        a1
22233        b1
22234        [EXCERPT]
22235        ˇ[FOLDED]
22236        [EXCERPT]
22237        [FOLDED]
22238        "
22239    });
22240    for _ in 0..5 {
22241        cx.simulate_keystroke("down");
22242        cx.assert_excerpts_with_selections(indoc! {"
22243            [EXCERPT]
22244            [FOLDED]
22245            [EXCERPT]
22246            a1
22247            b1
22248            [EXCERPT]
22249            [FOLDED]
22250            [EXCERPT]
22251            ˇ[FOLDED]
22252            "
22253        });
22254    }
22255
22256    cx.simulate_keystroke("up");
22257    cx.assert_excerpts_with_selections(indoc! {"
22258        [EXCERPT]
22259        [FOLDED]
22260        [EXCERPT]
22261        a1
22262        b1
22263        [EXCERPT]
22264        ˇ[FOLDED]
22265        [EXCERPT]
22266        [FOLDED]
22267        "
22268    });
22269    cx.simulate_keystroke("up");
22270    cx.assert_excerpts_with_selections(indoc! {"
22271        [EXCERPT]
22272        [FOLDED]
22273        [EXCERPT]
22274        a1
22275        b1
22276        ˇ[EXCERPT]
22277        [FOLDED]
22278        [EXCERPT]
22279        [FOLDED]
22280        "
22281    });
22282    cx.simulate_keystroke("up");
22283    cx.assert_excerpts_with_selections(indoc! {"
22284        [EXCERPT]
22285        [FOLDED]
22286        [EXCERPT]
22287        a1
22288        ˇb1
22289        [EXCERPT]
22290        [FOLDED]
22291        [EXCERPT]
22292        [FOLDED]
22293        "
22294    });
22295    cx.simulate_keystroke("up");
22296    cx.assert_excerpts_with_selections(indoc! {"
22297        [EXCERPT]
22298        [FOLDED]
22299        [EXCERPT]
22300        ˇa1
22301        b1
22302        [EXCERPT]
22303        [FOLDED]
22304        [EXCERPT]
22305        [FOLDED]
22306        "
22307    });
22308    for _ in 0..5 {
22309        cx.simulate_keystroke("up");
22310        cx.assert_excerpts_with_selections(indoc! {"
22311            [EXCERPT]
22312            ˇ[FOLDED]
22313            [EXCERPT]
22314            a1
22315            b1
22316            [EXCERPT]
22317            [FOLDED]
22318            [EXCERPT]
22319            [FOLDED]
22320            "
22321        });
22322    }
22323}
22324
22325#[gpui::test]
22326async fn test_edit_prediction_text(cx: &mut TestAppContext) {
22327    init_test(cx, |_| {});
22328
22329    // Simple insertion
22330    assert_highlighted_edits(
22331        "Hello, world!",
22332        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
22333        true,
22334        cx,
22335        |highlighted_edits, cx| {
22336            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
22337            assert_eq!(highlighted_edits.highlights.len(), 1);
22338            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
22339            assert_eq!(
22340                highlighted_edits.highlights[0].1.background_color,
22341                Some(cx.theme().status().created_background)
22342            );
22343        },
22344    )
22345    .await;
22346
22347    // Replacement
22348    assert_highlighted_edits(
22349        "This is a test.",
22350        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
22351        false,
22352        cx,
22353        |highlighted_edits, cx| {
22354            assert_eq!(highlighted_edits.text, "That is a test.");
22355            assert_eq!(highlighted_edits.highlights.len(), 1);
22356            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
22357            assert_eq!(
22358                highlighted_edits.highlights[0].1.background_color,
22359                Some(cx.theme().status().created_background)
22360            );
22361        },
22362    )
22363    .await;
22364
22365    // Multiple edits
22366    assert_highlighted_edits(
22367        "Hello, world!",
22368        vec![
22369            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
22370            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
22371        ],
22372        false,
22373        cx,
22374        |highlighted_edits, cx| {
22375            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
22376            assert_eq!(highlighted_edits.highlights.len(), 2);
22377            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
22378            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
22379            assert_eq!(
22380                highlighted_edits.highlights[0].1.background_color,
22381                Some(cx.theme().status().created_background)
22382            );
22383            assert_eq!(
22384                highlighted_edits.highlights[1].1.background_color,
22385                Some(cx.theme().status().created_background)
22386            );
22387        },
22388    )
22389    .await;
22390
22391    // Multiple lines with edits
22392    assert_highlighted_edits(
22393        "First line\nSecond line\nThird line\nFourth line",
22394        vec![
22395            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
22396            (
22397                Point::new(2, 0)..Point::new(2, 10),
22398                "New third line".to_string(),
22399            ),
22400            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
22401        ],
22402        false,
22403        cx,
22404        |highlighted_edits, cx| {
22405            assert_eq!(
22406                highlighted_edits.text,
22407                "Second modified\nNew third line\nFourth updated line"
22408            );
22409            assert_eq!(highlighted_edits.highlights.len(), 3);
22410            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
22411            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
22412            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
22413            for highlight in &highlighted_edits.highlights {
22414                assert_eq!(
22415                    highlight.1.background_color,
22416                    Some(cx.theme().status().created_background)
22417                );
22418            }
22419        },
22420    )
22421    .await;
22422}
22423
22424#[gpui::test]
22425async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
22426    init_test(cx, |_| {});
22427
22428    // Deletion
22429    assert_highlighted_edits(
22430        "Hello, world!",
22431        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
22432        true,
22433        cx,
22434        |highlighted_edits, cx| {
22435            assert_eq!(highlighted_edits.text, "Hello, world!");
22436            assert_eq!(highlighted_edits.highlights.len(), 1);
22437            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
22438            assert_eq!(
22439                highlighted_edits.highlights[0].1.background_color,
22440                Some(cx.theme().status().deleted_background)
22441            );
22442        },
22443    )
22444    .await;
22445
22446    // Insertion
22447    assert_highlighted_edits(
22448        "Hello, world!",
22449        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
22450        true,
22451        cx,
22452        |highlighted_edits, cx| {
22453            assert_eq!(highlighted_edits.highlights.len(), 1);
22454            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
22455            assert_eq!(
22456                highlighted_edits.highlights[0].1.background_color,
22457                Some(cx.theme().status().created_background)
22458            );
22459        },
22460    )
22461    .await;
22462}
22463
22464async fn assert_highlighted_edits(
22465    text: &str,
22466    edits: Vec<(Range<Point>, String)>,
22467    include_deletions: bool,
22468    cx: &mut TestAppContext,
22469    assertion_fn: impl Fn(HighlightedText, &App),
22470) {
22471    let window = cx.add_window(|window, cx| {
22472        let buffer = MultiBuffer::build_simple(text, cx);
22473        Editor::new(EditorMode::full(), buffer, None, window, cx)
22474    });
22475    let cx = &mut VisualTestContext::from_window(*window, cx);
22476
22477    let (buffer, snapshot) = window
22478        .update(cx, |editor, _window, cx| {
22479            (
22480                editor.buffer().clone(),
22481                editor.buffer().read(cx).snapshot(cx),
22482            )
22483        })
22484        .unwrap();
22485
22486    let edits = edits
22487        .into_iter()
22488        .map(|(range, edit)| {
22489            (
22490                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
22491                edit,
22492            )
22493        })
22494        .collect::<Vec<_>>();
22495
22496    let text_anchor_edits = edits
22497        .clone()
22498        .into_iter()
22499        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
22500        .collect::<Vec<_>>();
22501
22502    let edit_preview = window
22503        .update(cx, |_, _window, cx| {
22504            buffer
22505                .read(cx)
22506                .as_singleton()
22507                .unwrap()
22508                .read(cx)
22509                .preview_edits(text_anchor_edits.into(), cx)
22510        })
22511        .unwrap()
22512        .await;
22513
22514    cx.update(|_window, cx| {
22515        let highlighted_edits = edit_prediction_edit_text(
22516            snapshot.as_singleton().unwrap().2,
22517            &edits,
22518            &edit_preview,
22519            include_deletions,
22520            cx,
22521        );
22522        assertion_fn(highlighted_edits, cx)
22523    });
22524}
22525
22526#[track_caller]
22527fn assert_breakpoint(
22528    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
22529    path: &Arc<Path>,
22530    expected: Vec<(u32, Breakpoint)>,
22531) {
22532    if expected.is_empty() {
22533        assert!(!breakpoints.contains_key(path), "{}", path.display());
22534    } else {
22535        let mut breakpoint = breakpoints
22536            .get(path)
22537            .unwrap()
22538            .iter()
22539            .map(|breakpoint| {
22540                (
22541                    breakpoint.row,
22542                    Breakpoint {
22543                        message: breakpoint.message.clone(),
22544                        state: breakpoint.state,
22545                        condition: breakpoint.condition.clone(),
22546                        hit_condition: breakpoint.hit_condition.clone(),
22547                    },
22548                )
22549            })
22550            .collect::<Vec<_>>();
22551
22552        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
22553
22554        assert_eq!(expected, breakpoint);
22555    }
22556}
22557
22558fn add_log_breakpoint_at_cursor(
22559    editor: &mut Editor,
22560    log_message: &str,
22561    window: &mut Window,
22562    cx: &mut Context<Editor>,
22563) {
22564    let (anchor, bp) = editor
22565        .breakpoints_at_cursors(window, cx)
22566        .first()
22567        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
22568        .unwrap_or_else(|| {
22569            let cursor_position: Point = editor.selections.newest(cx).head();
22570
22571            let breakpoint_position = editor
22572                .snapshot(window, cx)
22573                .display_snapshot
22574                .buffer_snapshot
22575                .anchor_before(Point::new(cursor_position.row, 0));
22576
22577            (breakpoint_position, Breakpoint::new_log(log_message))
22578        });
22579
22580    editor.edit_breakpoint_at_anchor(
22581        anchor,
22582        bp,
22583        BreakpointEditAction::EditLogMessage(log_message.into()),
22584        cx,
22585    );
22586}
22587
22588#[gpui::test]
22589async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
22590    init_test(cx, |_| {});
22591
22592    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22593    let fs = FakeFs::new(cx.executor());
22594    fs.insert_tree(
22595        path!("/a"),
22596        json!({
22597            "main.rs": sample_text,
22598        }),
22599    )
22600    .await;
22601    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22602    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22603    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22604
22605    let fs = FakeFs::new(cx.executor());
22606    fs.insert_tree(
22607        path!("/a"),
22608        json!({
22609            "main.rs": sample_text,
22610        }),
22611    )
22612    .await;
22613    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22614    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22615    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22616    let worktree_id = workspace
22617        .update(cx, |workspace, _window, cx| {
22618            workspace.project().update(cx, |project, cx| {
22619                project.worktrees(cx).next().unwrap().read(cx).id()
22620            })
22621        })
22622        .unwrap();
22623
22624    let buffer = project
22625        .update(cx, |project, cx| {
22626            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22627        })
22628        .await
22629        .unwrap();
22630
22631    let (editor, cx) = cx.add_window_view(|window, cx| {
22632        Editor::new(
22633            EditorMode::full(),
22634            MultiBuffer::build_from_buffer(buffer, cx),
22635            Some(project.clone()),
22636            window,
22637            cx,
22638        )
22639    });
22640
22641    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22642    let abs_path = project.read_with(cx, |project, cx| {
22643        project
22644            .absolute_path(&project_path, cx)
22645            .map(Arc::from)
22646            .unwrap()
22647    });
22648
22649    // assert we can add breakpoint on the first line
22650    editor.update_in(cx, |editor, window, cx| {
22651        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22652        editor.move_to_end(&MoveToEnd, window, cx);
22653        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22654    });
22655
22656    let breakpoints = editor.update(cx, |editor, cx| {
22657        editor
22658            .breakpoint_store()
22659            .as_ref()
22660            .unwrap()
22661            .read(cx)
22662            .all_source_breakpoints(cx)
22663    });
22664
22665    assert_eq!(1, breakpoints.len());
22666    assert_breakpoint(
22667        &breakpoints,
22668        &abs_path,
22669        vec![
22670            (0, Breakpoint::new_standard()),
22671            (3, Breakpoint::new_standard()),
22672        ],
22673    );
22674
22675    editor.update_in(cx, |editor, window, cx| {
22676        editor.move_to_beginning(&MoveToBeginning, window, cx);
22677        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22678    });
22679
22680    let breakpoints = editor.update(cx, |editor, cx| {
22681        editor
22682            .breakpoint_store()
22683            .as_ref()
22684            .unwrap()
22685            .read(cx)
22686            .all_source_breakpoints(cx)
22687    });
22688
22689    assert_eq!(1, breakpoints.len());
22690    assert_breakpoint(
22691        &breakpoints,
22692        &abs_path,
22693        vec![(3, Breakpoint::new_standard())],
22694    );
22695
22696    editor.update_in(cx, |editor, window, cx| {
22697        editor.move_to_end(&MoveToEnd, window, cx);
22698        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22699    });
22700
22701    let breakpoints = editor.update(cx, |editor, cx| {
22702        editor
22703            .breakpoint_store()
22704            .as_ref()
22705            .unwrap()
22706            .read(cx)
22707            .all_source_breakpoints(cx)
22708    });
22709
22710    assert_eq!(0, breakpoints.len());
22711    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22712}
22713
22714#[gpui::test]
22715async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
22716    init_test(cx, |_| {});
22717
22718    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22719
22720    let fs = FakeFs::new(cx.executor());
22721    fs.insert_tree(
22722        path!("/a"),
22723        json!({
22724            "main.rs": sample_text,
22725        }),
22726    )
22727    .await;
22728    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22729    let (workspace, cx) =
22730        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22731
22732    let worktree_id = workspace.update(cx, |workspace, cx| {
22733        workspace.project().update(cx, |project, cx| {
22734            project.worktrees(cx).next().unwrap().read(cx).id()
22735        })
22736    });
22737
22738    let buffer = project
22739        .update(cx, |project, cx| {
22740            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22741        })
22742        .await
22743        .unwrap();
22744
22745    let (editor, cx) = cx.add_window_view(|window, cx| {
22746        Editor::new(
22747            EditorMode::full(),
22748            MultiBuffer::build_from_buffer(buffer, cx),
22749            Some(project.clone()),
22750            window,
22751            cx,
22752        )
22753    });
22754
22755    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22756    let abs_path = project.read_with(cx, |project, cx| {
22757        project
22758            .absolute_path(&project_path, cx)
22759            .map(Arc::from)
22760            .unwrap()
22761    });
22762
22763    editor.update_in(cx, |editor, window, cx| {
22764        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22765    });
22766
22767    let breakpoints = editor.update(cx, |editor, cx| {
22768        editor
22769            .breakpoint_store()
22770            .as_ref()
22771            .unwrap()
22772            .read(cx)
22773            .all_source_breakpoints(cx)
22774    });
22775
22776    assert_breakpoint(
22777        &breakpoints,
22778        &abs_path,
22779        vec![(0, Breakpoint::new_log("hello world"))],
22780    );
22781
22782    // Removing a log message from a log breakpoint should remove it
22783    editor.update_in(cx, |editor, window, cx| {
22784        add_log_breakpoint_at_cursor(editor, "", window, cx);
22785    });
22786
22787    let breakpoints = editor.update(cx, |editor, cx| {
22788        editor
22789            .breakpoint_store()
22790            .as_ref()
22791            .unwrap()
22792            .read(cx)
22793            .all_source_breakpoints(cx)
22794    });
22795
22796    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22797
22798    editor.update_in(cx, |editor, window, cx| {
22799        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22800        editor.move_to_end(&MoveToEnd, window, cx);
22801        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22802        // Not adding a log message to a standard breakpoint shouldn't remove it
22803        add_log_breakpoint_at_cursor(editor, "", window, cx);
22804    });
22805
22806    let breakpoints = editor.update(cx, |editor, cx| {
22807        editor
22808            .breakpoint_store()
22809            .as_ref()
22810            .unwrap()
22811            .read(cx)
22812            .all_source_breakpoints(cx)
22813    });
22814
22815    assert_breakpoint(
22816        &breakpoints,
22817        &abs_path,
22818        vec![
22819            (0, Breakpoint::new_standard()),
22820            (3, Breakpoint::new_standard()),
22821        ],
22822    );
22823
22824    editor.update_in(cx, |editor, window, cx| {
22825        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22826    });
22827
22828    let breakpoints = editor.update(cx, |editor, cx| {
22829        editor
22830            .breakpoint_store()
22831            .as_ref()
22832            .unwrap()
22833            .read(cx)
22834            .all_source_breakpoints(cx)
22835    });
22836
22837    assert_breakpoint(
22838        &breakpoints,
22839        &abs_path,
22840        vec![
22841            (0, Breakpoint::new_standard()),
22842            (3, Breakpoint::new_log("hello world")),
22843        ],
22844    );
22845
22846    editor.update_in(cx, |editor, window, cx| {
22847        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
22848    });
22849
22850    let breakpoints = editor.update(cx, |editor, cx| {
22851        editor
22852            .breakpoint_store()
22853            .as_ref()
22854            .unwrap()
22855            .read(cx)
22856            .all_source_breakpoints(cx)
22857    });
22858
22859    assert_breakpoint(
22860        &breakpoints,
22861        &abs_path,
22862        vec![
22863            (0, Breakpoint::new_standard()),
22864            (3, Breakpoint::new_log("hello Earth!!")),
22865        ],
22866    );
22867}
22868
22869/// This also tests that Editor::breakpoint_at_cursor_head is working properly
22870/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
22871/// or when breakpoints were placed out of order. This tests for a regression too
22872#[gpui::test]
22873async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
22874    init_test(cx, |_| {});
22875
22876    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22877    let fs = FakeFs::new(cx.executor());
22878    fs.insert_tree(
22879        path!("/a"),
22880        json!({
22881            "main.rs": sample_text,
22882        }),
22883    )
22884    .await;
22885    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22886    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22887    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22888
22889    let fs = FakeFs::new(cx.executor());
22890    fs.insert_tree(
22891        path!("/a"),
22892        json!({
22893            "main.rs": sample_text,
22894        }),
22895    )
22896    .await;
22897    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22898    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22899    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22900    let worktree_id = workspace
22901        .update(cx, |workspace, _window, cx| {
22902            workspace.project().update(cx, |project, cx| {
22903                project.worktrees(cx).next().unwrap().read(cx).id()
22904            })
22905        })
22906        .unwrap();
22907
22908    let buffer = project
22909        .update(cx, |project, cx| {
22910            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22911        })
22912        .await
22913        .unwrap();
22914
22915    let (editor, cx) = cx.add_window_view(|window, cx| {
22916        Editor::new(
22917            EditorMode::full(),
22918            MultiBuffer::build_from_buffer(buffer, cx),
22919            Some(project.clone()),
22920            window,
22921            cx,
22922        )
22923    });
22924
22925    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22926    let abs_path = project.read_with(cx, |project, cx| {
22927        project
22928            .absolute_path(&project_path, cx)
22929            .map(Arc::from)
22930            .unwrap()
22931    });
22932
22933    // assert we can add breakpoint on the first line
22934    editor.update_in(cx, |editor, window, cx| {
22935        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22936        editor.move_to_end(&MoveToEnd, window, cx);
22937        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22938        editor.move_up(&MoveUp, window, cx);
22939        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22940    });
22941
22942    let breakpoints = editor.update(cx, |editor, cx| {
22943        editor
22944            .breakpoint_store()
22945            .as_ref()
22946            .unwrap()
22947            .read(cx)
22948            .all_source_breakpoints(cx)
22949    });
22950
22951    assert_eq!(1, breakpoints.len());
22952    assert_breakpoint(
22953        &breakpoints,
22954        &abs_path,
22955        vec![
22956            (0, Breakpoint::new_standard()),
22957            (2, Breakpoint::new_standard()),
22958            (3, Breakpoint::new_standard()),
22959        ],
22960    );
22961
22962    editor.update_in(cx, |editor, window, cx| {
22963        editor.move_to_beginning(&MoveToBeginning, window, cx);
22964        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22965        editor.move_to_end(&MoveToEnd, window, cx);
22966        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22967        // Disabling a breakpoint that doesn't exist should do nothing
22968        editor.move_up(&MoveUp, window, cx);
22969        editor.move_up(&MoveUp, window, cx);
22970        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22971    });
22972
22973    let breakpoints = editor.update(cx, |editor, cx| {
22974        editor
22975            .breakpoint_store()
22976            .as_ref()
22977            .unwrap()
22978            .read(cx)
22979            .all_source_breakpoints(cx)
22980    });
22981
22982    let disable_breakpoint = {
22983        let mut bp = Breakpoint::new_standard();
22984        bp.state = BreakpointState::Disabled;
22985        bp
22986    };
22987
22988    assert_eq!(1, breakpoints.len());
22989    assert_breakpoint(
22990        &breakpoints,
22991        &abs_path,
22992        vec![
22993            (0, disable_breakpoint.clone()),
22994            (2, Breakpoint::new_standard()),
22995            (3, disable_breakpoint.clone()),
22996        ],
22997    );
22998
22999    editor.update_in(cx, |editor, window, cx| {
23000        editor.move_to_beginning(&MoveToBeginning, window, cx);
23001        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23002        editor.move_to_end(&MoveToEnd, window, cx);
23003        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23004        editor.move_up(&MoveUp, window, cx);
23005        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23006    });
23007
23008    let breakpoints = editor.update(cx, |editor, cx| {
23009        editor
23010            .breakpoint_store()
23011            .as_ref()
23012            .unwrap()
23013            .read(cx)
23014            .all_source_breakpoints(cx)
23015    });
23016
23017    assert_eq!(1, breakpoints.len());
23018    assert_breakpoint(
23019        &breakpoints,
23020        &abs_path,
23021        vec![
23022            (0, Breakpoint::new_standard()),
23023            (2, disable_breakpoint),
23024            (3, Breakpoint::new_standard()),
23025        ],
23026    );
23027}
23028
23029#[gpui::test]
23030async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
23031    init_test(cx, |_| {});
23032    let capabilities = lsp::ServerCapabilities {
23033        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
23034            prepare_provider: Some(true),
23035            work_done_progress_options: Default::default(),
23036        })),
23037        ..Default::default()
23038    };
23039    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23040
23041    cx.set_state(indoc! {"
23042        struct Fˇoo {}
23043    "});
23044
23045    cx.update_editor(|editor, _, cx| {
23046        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23047        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23048        editor.highlight_background::<DocumentHighlightRead>(
23049            &[highlight_range],
23050            |theme| theme.colors().editor_document_highlight_read_background,
23051            cx,
23052        );
23053    });
23054
23055    let mut prepare_rename_handler = cx
23056        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
23057            move |_, _, _| async move {
23058                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
23059                    start: lsp::Position {
23060                        line: 0,
23061                        character: 7,
23062                    },
23063                    end: lsp::Position {
23064                        line: 0,
23065                        character: 10,
23066                    },
23067                })))
23068            },
23069        );
23070    let prepare_rename_task = cx
23071        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23072        .expect("Prepare rename was not started");
23073    prepare_rename_handler.next().await.unwrap();
23074    prepare_rename_task.await.expect("Prepare rename failed");
23075
23076    let mut rename_handler =
23077        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23078            let edit = lsp::TextEdit {
23079                range: lsp::Range {
23080                    start: lsp::Position {
23081                        line: 0,
23082                        character: 7,
23083                    },
23084                    end: lsp::Position {
23085                        line: 0,
23086                        character: 10,
23087                    },
23088                },
23089                new_text: "FooRenamed".to_string(),
23090            };
23091            Ok(Some(lsp::WorkspaceEdit::new(
23092                // Specify the same edit twice
23093                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
23094            )))
23095        });
23096    let rename_task = cx
23097        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23098        .expect("Confirm rename was not started");
23099    rename_handler.next().await.unwrap();
23100    rename_task.await.expect("Confirm rename failed");
23101    cx.run_until_parked();
23102
23103    // Despite two edits, only one is actually applied as those are identical
23104    cx.assert_editor_state(indoc! {"
23105        struct FooRenamedˇ {}
23106    "});
23107}
23108
23109#[gpui::test]
23110async fn test_rename_without_prepare(cx: &mut TestAppContext) {
23111    init_test(cx, |_| {});
23112    // These capabilities indicate that the server does not support prepare rename.
23113    let capabilities = lsp::ServerCapabilities {
23114        rename_provider: Some(lsp::OneOf::Left(true)),
23115        ..Default::default()
23116    };
23117    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23118
23119    cx.set_state(indoc! {"
23120        struct Fˇoo {}
23121    "});
23122
23123    cx.update_editor(|editor, _window, cx| {
23124        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23125        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23126        editor.highlight_background::<DocumentHighlightRead>(
23127            &[highlight_range],
23128            |theme| theme.colors().editor_document_highlight_read_background,
23129            cx,
23130        );
23131    });
23132
23133    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23134        .expect("Prepare rename was not started")
23135        .await
23136        .expect("Prepare rename failed");
23137
23138    let mut rename_handler =
23139        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23140            let edit = lsp::TextEdit {
23141                range: lsp::Range {
23142                    start: lsp::Position {
23143                        line: 0,
23144                        character: 7,
23145                    },
23146                    end: lsp::Position {
23147                        line: 0,
23148                        character: 10,
23149                    },
23150                },
23151                new_text: "FooRenamed".to_string(),
23152            };
23153            Ok(Some(lsp::WorkspaceEdit::new(
23154                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23155            )))
23156        });
23157    let rename_task = cx
23158        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23159        .expect("Confirm rename was not started");
23160    rename_handler.next().await.unwrap();
23161    rename_task.await.expect("Confirm rename failed");
23162    cx.run_until_parked();
23163
23164    // Correct range is renamed, as `surrounding_word` is used to find it.
23165    cx.assert_editor_state(indoc! {"
23166        struct FooRenamedˇ {}
23167    "});
23168}
23169
23170#[gpui::test]
23171async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23172    init_test(cx, |_| {});
23173    let mut cx = EditorTestContext::new(cx).await;
23174
23175    let language = Arc::new(
23176        Language::new(
23177            LanguageConfig::default(),
23178            Some(tree_sitter_html::LANGUAGE.into()),
23179        )
23180        .with_brackets_query(
23181            r#"
23182            ("<" @open "/>" @close)
23183            ("</" @open ">" @close)
23184            ("<" @open ">" @close)
23185            ("\"" @open "\"" @close)
23186            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23187        "#,
23188        )
23189        .unwrap(),
23190    );
23191    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23192
23193    cx.set_state(indoc! {"
23194        <span>ˇ</span>
23195    "});
23196    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23197    cx.assert_editor_state(indoc! {"
23198        <span>
23199        ˇ
23200        </span>
23201    "});
23202
23203    cx.set_state(indoc! {"
23204        <span><span></span>ˇ</span>
23205    "});
23206    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23207    cx.assert_editor_state(indoc! {"
23208        <span><span></span>
23209        ˇ</span>
23210    "});
23211
23212    cx.set_state(indoc! {"
23213        <span>ˇ
23214        </span>
23215    "});
23216    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23217    cx.assert_editor_state(indoc! {"
23218        <span>
23219        ˇ
23220        </span>
23221    "});
23222}
23223
23224#[gpui::test(iterations = 10)]
23225async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23226    init_test(cx, |_| {});
23227
23228    let fs = FakeFs::new(cx.executor());
23229    fs.insert_tree(
23230        path!("/dir"),
23231        json!({
23232            "a.ts": "a",
23233        }),
23234    )
23235    .await;
23236
23237    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23238    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23239    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23240
23241    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23242    language_registry.add(Arc::new(Language::new(
23243        LanguageConfig {
23244            name: "TypeScript".into(),
23245            matcher: LanguageMatcher {
23246                path_suffixes: vec!["ts".to_string()],
23247                ..Default::default()
23248            },
23249            ..Default::default()
23250        },
23251        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
23252    )));
23253    let mut fake_language_servers = language_registry.register_fake_lsp(
23254        "TypeScript",
23255        FakeLspAdapter {
23256            capabilities: lsp::ServerCapabilities {
23257                code_lens_provider: Some(lsp::CodeLensOptions {
23258                    resolve_provider: Some(true),
23259                }),
23260                execute_command_provider: Some(lsp::ExecuteCommandOptions {
23261                    commands: vec!["_the/command".to_string()],
23262                    ..lsp::ExecuteCommandOptions::default()
23263                }),
23264                ..lsp::ServerCapabilities::default()
23265            },
23266            ..FakeLspAdapter::default()
23267        },
23268    );
23269
23270    let editor = workspace
23271        .update(cx, |workspace, window, cx| {
23272            workspace.open_abs_path(
23273                PathBuf::from(path!("/dir/a.ts")),
23274                OpenOptions::default(),
23275                window,
23276                cx,
23277            )
23278        })
23279        .unwrap()
23280        .await
23281        .unwrap()
23282        .downcast::<Editor>()
23283        .unwrap();
23284    cx.executor().run_until_parked();
23285
23286    let fake_server = fake_language_servers.next().await.unwrap();
23287
23288    let buffer = editor.update(cx, |editor, cx| {
23289        editor
23290            .buffer()
23291            .read(cx)
23292            .as_singleton()
23293            .expect("have opened a single file by path")
23294    });
23295
23296    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
23297    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
23298    drop(buffer_snapshot);
23299    let actions = cx
23300        .update_window(*workspace, |_, window, cx| {
23301            project.code_actions(&buffer, anchor..anchor, window, cx)
23302        })
23303        .unwrap();
23304
23305    fake_server
23306        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23307            Ok(Some(vec![
23308                lsp::CodeLens {
23309                    range: lsp::Range::default(),
23310                    command: Some(lsp::Command {
23311                        title: "Code lens command".to_owned(),
23312                        command: "_the/command".to_owned(),
23313                        arguments: None,
23314                    }),
23315                    data: None,
23316                },
23317                lsp::CodeLens {
23318                    range: lsp::Range::default(),
23319                    command: Some(lsp::Command {
23320                        title: "Command not in capabilities".to_owned(),
23321                        command: "not in capabilities".to_owned(),
23322                        arguments: None,
23323                    }),
23324                    data: None,
23325                },
23326                lsp::CodeLens {
23327                    range: lsp::Range {
23328                        start: lsp::Position {
23329                            line: 1,
23330                            character: 1,
23331                        },
23332                        end: lsp::Position {
23333                            line: 1,
23334                            character: 1,
23335                        },
23336                    },
23337                    command: Some(lsp::Command {
23338                        title: "Command not in range".to_owned(),
23339                        command: "_the/command".to_owned(),
23340                        arguments: None,
23341                    }),
23342                    data: None,
23343                },
23344            ]))
23345        })
23346        .next()
23347        .await;
23348
23349    let actions = actions.await.unwrap();
23350    assert_eq!(
23351        actions.len(),
23352        1,
23353        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
23354    );
23355    let action = actions[0].clone();
23356    let apply = project.update(cx, |project, cx| {
23357        project.apply_code_action(buffer.clone(), action, true, cx)
23358    });
23359
23360    // Resolving the code action does not populate its edits. In absence of
23361    // edits, we must execute the given command.
23362    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
23363        |mut lens, _| async move {
23364            let lens_command = lens.command.as_mut().expect("should have a command");
23365            assert_eq!(lens_command.title, "Code lens command");
23366            lens_command.arguments = Some(vec![json!("the-argument")]);
23367            Ok(lens)
23368        },
23369    );
23370
23371    // While executing the command, the language server sends the editor
23372    // a `workspaceEdit` request.
23373    fake_server
23374        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
23375            let fake = fake_server.clone();
23376            move |params, _| {
23377                assert_eq!(params.command, "_the/command");
23378                let fake = fake.clone();
23379                async move {
23380                    fake.server
23381                        .request::<lsp::request::ApplyWorkspaceEdit>(
23382                            lsp::ApplyWorkspaceEditParams {
23383                                label: None,
23384                                edit: lsp::WorkspaceEdit {
23385                                    changes: Some(
23386                                        [(
23387                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
23388                                            vec![lsp::TextEdit {
23389                                                range: lsp::Range::new(
23390                                                    lsp::Position::new(0, 0),
23391                                                    lsp::Position::new(0, 0),
23392                                                ),
23393                                                new_text: "X".into(),
23394                                            }],
23395                                        )]
23396                                        .into_iter()
23397                                        .collect(),
23398                                    ),
23399                                    ..lsp::WorkspaceEdit::default()
23400                                },
23401                            },
23402                        )
23403                        .await
23404                        .into_response()
23405                        .unwrap();
23406                    Ok(Some(json!(null)))
23407                }
23408            }
23409        })
23410        .next()
23411        .await;
23412
23413    // Applying the code lens command returns a project transaction containing the edits
23414    // sent by the language server in its `workspaceEdit` request.
23415    let transaction = apply.await.unwrap();
23416    assert!(transaction.0.contains_key(&buffer));
23417    buffer.update(cx, |buffer, cx| {
23418        assert_eq!(buffer.text(), "Xa");
23419        buffer.undo(cx);
23420        assert_eq!(buffer.text(), "a");
23421    });
23422
23423    let actions_after_edits = cx
23424        .update_window(*workspace, |_, window, cx| {
23425            project.code_actions(&buffer, anchor..anchor, window, cx)
23426        })
23427        .unwrap()
23428        .await
23429        .unwrap();
23430    assert_eq!(
23431        actions, actions_after_edits,
23432        "For the same selection, same code lens actions should be returned"
23433    );
23434
23435    let _responses =
23436        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23437            panic!("No more code lens requests are expected");
23438        });
23439    editor.update_in(cx, |editor, window, cx| {
23440        editor.select_all(&SelectAll, window, cx);
23441    });
23442    cx.executor().run_until_parked();
23443    let new_actions = cx
23444        .update_window(*workspace, |_, window, cx| {
23445            project.code_actions(&buffer, anchor..anchor, window, cx)
23446        })
23447        .unwrap()
23448        .await
23449        .unwrap();
23450    assert_eq!(
23451        actions, new_actions,
23452        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
23453    );
23454}
23455
23456#[gpui::test]
23457async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
23458    init_test(cx, |_| {});
23459
23460    let fs = FakeFs::new(cx.executor());
23461    let main_text = r#"fn main() {
23462println!("1");
23463println!("2");
23464println!("3");
23465println!("4");
23466println!("5");
23467}"#;
23468    let lib_text = "mod foo {}";
23469    fs.insert_tree(
23470        path!("/a"),
23471        json!({
23472            "lib.rs": lib_text,
23473            "main.rs": main_text,
23474        }),
23475    )
23476    .await;
23477
23478    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23479    let (workspace, cx) =
23480        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23481    let worktree_id = workspace.update(cx, |workspace, cx| {
23482        workspace.project().update(cx, |project, cx| {
23483            project.worktrees(cx).next().unwrap().read(cx).id()
23484        })
23485    });
23486
23487    let expected_ranges = vec![
23488        Point::new(0, 0)..Point::new(0, 0),
23489        Point::new(1, 0)..Point::new(1, 1),
23490        Point::new(2, 0)..Point::new(2, 2),
23491        Point::new(3, 0)..Point::new(3, 3),
23492    ];
23493
23494    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23495    let editor_1 = workspace
23496        .update_in(cx, |workspace, window, cx| {
23497            workspace.open_path(
23498                (worktree_id, rel_path("main.rs")),
23499                Some(pane_1.downgrade()),
23500                true,
23501                window,
23502                cx,
23503            )
23504        })
23505        .unwrap()
23506        .await
23507        .downcast::<Editor>()
23508        .unwrap();
23509    pane_1.update(cx, |pane, cx| {
23510        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23511        open_editor.update(cx, |editor, cx| {
23512            assert_eq!(
23513                editor.display_text(cx),
23514                main_text,
23515                "Original main.rs text on initial open",
23516            );
23517            assert_eq!(
23518                editor
23519                    .selections
23520                    .all::<Point>(cx)
23521                    .into_iter()
23522                    .map(|s| s.range())
23523                    .collect::<Vec<_>>(),
23524                vec![Point::zero()..Point::zero()],
23525                "Default selections on initial open",
23526            );
23527        })
23528    });
23529    editor_1.update_in(cx, |editor, window, cx| {
23530        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23531            s.select_ranges(expected_ranges.clone());
23532        });
23533    });
23534
23535    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
23536        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
23537    });
23538    let editor_2 = workspace
23539        .update_in(cx, |workspace, window, cx| {
23540            workspace.open_path(
23541                (worktree_id, rel_path("main.rs")),
23542                Some(pane_2.downgrade()),
23543                true,
23544                window,
23545                cx,
23546            )
23547        })
23548        .unwrap()
23549        .await
23550        .downcast::<Editor>()
23551        .unwrap();
23552    pane_2.update(cx, |pane, cx| {
23553        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23554        open_editor.update(cx, |editor, cx| {
23555            assert_eq!(
23556                editor.display_text(cx),
23557                main_text,
23558                "Original main.rs text on initial open in another panel",
23559            );
23560            assert_eq!(
23561                editor
23562                    .selections
23563                    .all::<Point>(cx)
23564                    .into_iter()
23565                    .map(|s| s.range())
23566                    .collect::<Vec<_>>(),
23567                vec![Point::zero()..Point::zero()],
23568                "Default selections on initial open in another panel",
23569            );
23570        })
23571    });
23572
23573    editor_2.update_in(cx, |editor, window, cx| {
23574        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23575    });
23576
23577    let _other_editor_1 = workspace
23578        .update_in(cx, |workspace, window, cx| {
23579            workspace.open_path(
23580                (worktree_id, rel_path("lib.rs")),
23581                Some(pane_1.downgrade()),
23582                true,
23583                window,
23584                cx,
23585            )
23586        })
23587        .unwrap()
23588        .await
23589        .downcast::<Editor>()
23590        .unwrap();
23591    pane_1
23592        .update_in(cx, |pane, window, cx| {
23593            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23594        })
23595        .await
23596        .unwrap();
23597    drop(editor_1);
23598    pane_1.update(cx, |pane, cx| {
23599        pane.active_item()
23600            .unwrap()
23601            .downcast::<Editor>()
23602            .unwrap()
23603            .update(cx, |editor, cx| {
23604                assert_eq!(
23605                    editor.display_text(cx),
23606                    lib_text,
23607                    "Other file should be open and active",
23608                );
23609            });
23610        assert_eq!(pane.items().count(), 1, "No other editors should be open");
23611    });
23612
23613    let _other_editor_2 = workspace
23614        .update_in(cx, |workspace, window, cx| {
23615            workspace.open_path(
23616                (worktree_id, rel_path("lib.rs")),
23617                Some(pane_2.downgrade()),
23618                true,
23619                window,
23620                cx,
23621            )
23622        })
23623        .unwrap()
23624        .await
23625        .downcast::<Editor>()
23626        .unwrap();
23627    pane_2
23628        .update_in(cx, |pane, window, cx| {
23629            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23630        })
23631        .await
23632        .unwrap();
23633    drop(editor_2);
23634    pane_2.update(cx, |pane, cx| {
23635        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23636        open_editor.update(cx, |editor, cx| {
23637            assert_eq!(
23638                editor.display_text(cx),
23639                lib_text,
23640                "Other file should be open and active in another panel too",
23641            );
23642        });
23643        assert_eq!(
23644            pane.items().count(),
23645            1,
23646            "No other editors should be open in another pane",
23647        );
23648    });
23649
23650    let _editor_1_reopened = workspace
23651        .update_in(cx, |workspace, window, cx| {
23652            workspace.open_path(
23653                (worktree_id, rel_path("main.rs")),
23654                Some(pane_1.downgrade()),
23655                true,
23656                window,
23657                cx,
23658            )
23659        })
23660        .unwrap()
23661        .await
23662        .downcast::<Editor>()
23663        .unwrap();
23664    let _editor_2_reopened = workspace
23665        .update_in(cx, |workspace, window, cx| {
23666            workspace.open_path(
23667                (worktree_id, rel_path("main.rs")),
23668                Some(pane_2.downgrade()),
23669                true,
23670                window,
23671                cx,
23672            )
23673        })
23674        .unwrap()
23675        .await
23676        .downcast::<Editor>()
23677        .unwrap();
23678    pane_1.update(cx, |pane, cx| {
23679        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23680        open_editor.update(cx, |editor, cx| {
23681            assert_eq!(
23682                editor.display_text(cx),
23683                main_text,
23684                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
23685            );
23686            assert_eq!(
23687                editor
23688                    .selections
23689                    .all::<Point>(cx)
23690                    .into_iter()
23691                    .map(|s| s.range())
23692                    .collect::<Vec<_>>(),
23693                expected_ranges,
23694                "Previous editor in the 1st panel had selections and should get them restored on reopen",
23695            );
23696        })
23697    });
23698    pane_2.update(cx, |pane, cx| {
23699        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23700        open_editor.update(cx, |editor, cx| {
23701            assert_eq!(
23702                editor.display_text(cx),
23703                r#"fn main() {
23704⋯rintln!("1");
23705⋯intln!("2");
23706⋯ntln!("3");
23707println!("4");
23708println!("5");
23709}"#,
23710                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
23711            );
23712            assert_eq!(
23713                editor
23714                    .selections
23715                    .all::<Point>(cx)
23716                    .into_iter()
23717                    .map(|s| s.range())
23718                    .collect::<Vec<_>>(),
23719                vec![Point::zero()..Point::zero()],
23720                "Previous editor in the 2nd pane had no selections changed hence should restore none",
23721            );
23722        })
23723    });
23724}
23725
23726#[gpui::test]
23727async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
23728    init_test(cx, |_| {});
23729
23730    let fs = FakeFs::new(cx.executor());
23731    let main_text = r#"fn main() {
23732println!("1");
23733println!("2");
23734println!("3");
23735println!("4");
23736println!("5");
23737}"#;
23738    let lib_text = "mod foo {}";
23739    fs.insert_tree(
23740        path!("/a"),
23741        json!({
23742            "lib.rs": lib_text,
23743            "main.rs": main_text,
23744        }),
23745    )
23746    .await;
23747
23748    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23749    let (workspace, cx) =
23750        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23751    let worktree_id = workspace.update(cx, |workspace, cx| {
23752        workspace.project().update(cx, |project, cx| {
23753            project.worktrees(cx).next().unwrap().read(cx).id()
23754        })
23755    });
23756
23757    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23758    let editor = workspace
23759        .update_in(cx, |workspace, window, cx| {
23760            workspace.open_path(
23761                (worktree_id, rel_path("main.rs")),
23762                Some(pane.downgrade()),
23763                true,
23764                window,
23765                cx,
23766            )
23767        })
23768        .unwrap()
23769        .await
23770        .downcast::<Editor>()
23771        .unwrap();
23772    pane.update(cx, |pane, cx| {
23773        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23774        open_editor.update(cx, |editor, cx| {
23775            assert_eq!(
23776                editor.display_text(cx),
23777                main_text,
23778                "Original main.rs text on initial open",
23779            );
23780        })
23781    });
23782    editor.update_in(cx, |editor, window, cx| {
23783        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
23784    });
23785
23786    cx.update_global(|store: &mut SettingsStore, cx| {
23787        store.update_user_settings(cx, |s| {
23788            s.workspace.restore_on_file_reopen = Some(false);
23789        });
23790    });
23791    editor.update_in(cx, |editor, window, cx| {
23792        editor.fold_ranges(
23793            vec![
23794                Point::new(1, 0)..Point::new(1, 1),
23795                Point::new(2, 0)..Point::new(2, 2),
23796                Point::new(3, 0)..Point::new(3, 3),
23797            ],
23798            false,
23799            window,
23800            cx,
23801        );
23802    });
23803    pane.update_in(cx, |pane, window, cx| {
23804        pane.close_all_items(&CloseAllItems::default(), window, cx)
23805    })
23806    .await
23807    .unwrap();
23808    pane.update(cx, |pane, _| {
23809        assert!(pane.active_item().is_none());
23810    });
23811    cx.update_global(|store: &mut SettingsStore, cx| {
23812        store.update_user_settings(cx, |s| {
23813            s.workspace.restore_on_file_reopen = Some(true);
23814        });
23815    });
23816
23817    let _editor_reopened = workspace
23818        .update_in(cx, |workspace, window, cx| {
23819            workspace.open_path(
23820                (worktree_id, rel_path("main.rs")),
23821                Some(pane.downgrade()),
23822                true,
23823                window,
23824                cx,
23825            )
23826        })
23827        .unwrap()
23828        .await
23829        .downcast::<Editor>()
23830        .unwrap();
23831    pane.update(cx, |pane, cx| {
23832        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23833        open_editor.update(cx, |editor, cx| {
23834            assert_eq!(
23835                editor.display_text(cx),
23836                main_text,
23837                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
23838            );
23839        })
23840    });
23841}
23842
23843#[gpui::test]
23844async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
23845    struct EmptyModalView {
23846        focus_handle: gpui::FocusHandle,
23847    }
23848    impl EventEmitter<DismissEvent> for EmptyModalView {}
23849    impl Render for EmptyModalView {
23850        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
23851            div()
23852        }
23853    }
23854    impl Focusable for EmptyModalView {
23855        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
23856            self.focus_handle.clone()
23857        }
23858    }
23859    impl workspace::ModalView for EmptyModalView {}
23860    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
23861        EmptyModalView {
23862            focus_handle: cx.focus_handle(),
23863        }
23864    }
23865
23866    init_test(cx, |_| {});
23867
23868    let fs = FakeFs::new(cx.executor());
23869    let project = Project::test(fs, [], cx).await;
23870    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23871    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
23872    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23873    let editor = cx.new_window_entity(|window, cx| {
23874        Editor::new(
23875            EditorMode::full(),
23876            buffer,
23877            Some(project.clone()),
23878            window,
23879            cx,
23880        )
23881    });
23882    workspace
23883        .update(cx, |workspace, window, cx| {
23884            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
23885        })
23886        .unwrap();
23887    editor.update_in(cx, |editor, window, cx| {
23888        editor.open_context_menu(&OpenContextMenu, window, cx);
23889        assert!(editor.mouse_context_menu.is_some());
23890    });
23891    workspace
23892        .update(cx, |workspace, window, cx| {
23893            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
23894        })
23895        .unwrap();
23896    cx.read(|cx| {
23897        assert!(editor.read(cx).mouse_context_menu.is_none());
23898    });
23899}
23900
23901fn set_linked_edit_ranges(
23902    opening: (Point, Point),
23903    closing: (Point, Point),
23904    editor: &mut Editor,
23905    cx: &mut Context<Editor>,
23906) {
23907    let Some((buffer, _)) = editor
23908        .buffer
23909        .read(cx)
23910        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
23911    else {
23912        panic!("Failed to get buffer for selection position");
23913    };
23914    let buffer = buffer.read(cx);
23915    let buffer_id = buffer.remote_id();
23916    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
23917    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
23918    let mut linked_ranges = HashMap::default();
23919    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
23920    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
23921}
23922
23923#[gpui::test]
23924async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
23925    init_test(cx, |_| {});
23926
23927    let fs = FakeFs::new(cx.executor());
23928    fs.insert_file(path!("/file.html"), Default::default())
23929        .await;
23930
23931    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
23932
23933    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23934    let html_language = Arc::new(Language::new(
23935        LanguageConfig {
23936            name: "HTML".into(),
23937            matcher: LanguageMatcher {
23938                path_suffixes: vec!["html".to_string()],
23939                ..LanguageMatcher::default()
23940            },
23941            brackets: BracketPairConfig {
23942                pairs: vec![BracketPair {
23943                    start: "<".into(),
23944                    end: ">".into(),
23945                    close: true,
23946                    ..Default::default()
23947                }],
23948                ..Default::default()
23949            },
23950            ..Default::default()
23951        },
23952        Some(tree_sitter_html::LANGUAGE.into()),
23953    ));
23954    language_registry.add(html_language);
23955    let mut fake_servers = language_registry.register_fake_lsp(
23956        "HTML",
23957        FakeLspAdapter {
23958            capabilities: lsp::ServerCapabilities {
23959                completion_provider: Some(lsp::CompletionOptions {
23960                    resolve_provider: Some(true),
23961                    ..Default::default()
23962                }),
23963                ..Default::default()
23964            },
23965            ..Default::default()
23966        },
23967    );
23968
23969    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23970    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23971
23972    let worktree_id = workspace
23973        .update(cx, |workspace, _window, cx| {
23974            workspace.project().update(cx, |project, cx| {
23975                project.worktrees(cx).next().unwrap().read(cx).id()
23976            })
23977        })
23978        .unwrap();
23979    project
23980        .update(cx, |project, cx| {
23981            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
23982        })
23983        .await
23984        .unwrap();
23985    let editor = workspace
23986        .update(cx, |workspace, window, cx| {
23987            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
23988        })
23989        .unwrap()
23990        .await
23991        .unwrap()
23992        .downcast::<Editor>()
23993        .unwrap();
23994
23995    let fake_server = fake_servers.next().await.unwrap();
23996    editor.update_in(cx, |editor, window, cx| {
23997        editor.set_text("<ad></ad>", window, cx);
23998        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23999            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
24000        });
24001        set_linked_edit_ranges(
24002            (Point::new(0, 1), Point::new(0, 3)),
24003            (Point::new(0, 6), Point::new(0, 8)),
24004            editor,
24005            cx,
24006        );
24007    });
24008    let mut completion_handle =
24009        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
24010            Ok(Some(lsp::CompletionResponse::Array(vec![
24011                lsp::CompletionItem {
24012                    label: "head".to_string(),
24013                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24014                        lsp::InsertReplaceEdit {
24015                            new_text: "head".to_string(),
24016                            insert: lsp::Range::new(
24017                                lsp::Position::new(0, 1),
24018                                lsp::Position::new(0, 3),
24019                            ),
24020                            replace: lsp::Range::new(
24021                                lsp::Position::new(0, 1),
24022                                lsp::Position::new(0, 3),
24023                            ),
24024                        },
24025                    )),
24026                    ..Default::default()
24027                },
24028            ])))
24029        });
24030    editor.update_in(cx, |editor, window, cx| {
24031        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
24032    });
24033    cx.run_until_parked();
24034    completion_handle.next().await.unwrap();
24035    editor.update(cx, |editor, _| {
24036        assert!(
24037            editor.context_menu_visible(),
24038            "Completion menu should be visible"
24039        );
24040    });
24041    editor.update_in(cx, |editor, window, cx| {
24042        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
24043    });
24044    cx.executor().run_until_parked();
24045    editor.update(cx, |editor, cx| {
24046        assert_eq!(editor.text(cx), "<head></head>");
24047    });
24048}
24049
24050#[gpui::test]
24051async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
24052    init_test(cx, |_| {});
24053
24054    let mut cx = EditorTestContext::new(cx).await;
24055    let language = Arc::new(Language::new(
24056        LanguageConfig {
24057            name: "TSX".into(),
24058            matcher: LanguageMatcher {
24059                path_suffixes: vec!["tsx".to_string()],
24060                ..LanguageMatcher::default()
24061            },
24062            brackets: BracketPairConfig {
24063                pairs: vec![BracketPair {
24064                    start: "<".into(),
24065                    end: ">".into(),
24066                    close: true,
24067                    ..Default::default()
24068                }],
24069                ..Default::default()
24070            },
24071            linked_edit_characters: HashSet::from_iter(['.']),
24072            ..Default::default()
24073        },
24074        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
24075    ));
24076    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24077
24078    // Test typing > does not extend linked pair
24079    cx.set_state("<divˇ<div></div>");
24080    cx.update_editor(|editor, _, cx| {
24081        set_linked_edit_ranges(
24082            (Point::new(0, 1), Point::new(0, 4)),
24083            (Point::new(0, 11), Point::new(0, 14)),
24084            editor,
24085            cx,
24086        );
24087    });
24088    cx.update_editor(|editor, window, cx| {
24089        editor.handle_input(">", window, cx);
24090    });
24091    cx.assert_editor_state("<div>ˇ<div></div>");
24092
24093    // Test typing . do extend linked pair
24094    cx.set_state("<Animatedˇ></Animated>");
24095    cx.update_editor(|editor, _, cx| {
24096        set_linked_edit_ranges(
24097            (Point::new(0, 1), Point::new(0, 9)),
24098            (Point::new(0, 12), Point::new(0, 20)),
24099            editor,
24100            cx,
24101        );
24102    });
24103    cx.update_editor(|editor, window, cx| {
24104        editor.handle_input(".", window, cx);
24105    });
24106    cx.assert_editor_state("<Animated.ˇ></Animated.>");
24107    cx.update_editor(|editor, _, cx| {
24108        set_linked_edit_ranges(
24109            (Point::new(0, 1), Point::new(0, 10)),
24110            (Point::new(0, 13), Point::new(0, 21)),
24111            editor,
24112            cx,
24113        );
24114    });
24115    cx.update_editor(|editor, window, cx| {
24116        editor.handle_input("V", window, cx);
24117    });
24118    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
24119}
24120
24121#[gpui::test]
24122async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
24123    init_test(cx, |_| {});
24124
24125    let fs = FakeFs::new(cx.executor());
24126    fs.insert_tree(
24127        path!("/root"),
24128        json!({
24129            "a": {
24130                "main.rs": "fn main() {}",
24131            },
24132            "foo": {
24133                "bar": {
24134                    "external_file.rs": "pub mod external {}",
24135                }
24136            }
24137        }),
24138    )
24139    .await;
24140
24141    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
24142    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24143    language_registry.add(rust_lang());
24144    let _fake_servers = language_registry.register_fake_lsp(
24145        "Rust",
24146        FakeLspAdapter {
24147            ..FakeLspAdapter::default()
24148        },
24149    );
24150    let (workspace, cx) =
24151        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24152    let worktree_id = workspace.update(cx, |workspace, cx| {
24153        workspace.project().update(cx, |project, cx| {
24154            project.worktrees(cx).next().unwrap().read(cx).id()
24155        })
24156    });
24157
24158    let assert_language_servers_count =
24159        |expected: usize, context: &str, cx: &mut VisualTestContext| {
24160            project.update(cx, |project, cx| {
24161                let current = project
24162                    .lsp_store()
24163                    .read(cx)
24164                    .as_local()
24165                    .unwrap()
24166                    .language_servers
24167                    .len();
24168                assert_eq!(expected, current, "{context}");
24169            });
24170        };
24171
24172    assert_language_servers_count(
24173        0,
24174        "No servers should be running before any file is open",
24175        cx,
24176    );
24177    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24178    let main_editor = workspace
24179        .update_in(cx, |workspace, window, cx| {
24180            workspace.open_path(
24181                (worktree_id, rel_path("main.rs")),
24182                Some(pane.downgrade()),
24183                true,
24184                window,
24185                cx,
24186            )
24187        })
24188        .unwrap()
24189        .await
24190        .downcast::<Editor>()
24191        .unwrap();
24192    pane.update(cx, |pane, cx| {
24193        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24194        open_editor.update(cx, |editor, cx| {
24195            assert_eq!(
24196                editor.display_text(cx),
24197                "fn main() {}",
24198                "Original main.rs text on initial open",
24199            );
24200        });
24201        assert_eq!(open_editor, main_editor);
24202    });
24203    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24204
24205    let external_editor = workspace
24206        .update_in(cx, |workspace, window, cx| {
24207            workspace.open_abs_path(
24208                PathBuf::from("/root/foo/bar/external_file.rs"),
24209                OpenOptions::default(),
24210                window,
24211                cx,
24212            )
24213        })
24214        .await
24215        .expect("opening external file")
24216        .downcast::<Editor>()
24217        .expect("downcasted external file's open element to editor");
24218    pane.update(cx, |pane, cx| {
24219        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24220        open_editor.update(cx, |editor, cx| {
24221            assert_eq!(
24222                editor.display_text(cx),
24223                "pub mod external {}",
24224                "External file is open now",
24225            );
24226        });
24227        assert_eq!(open_editor, external_editor);
24228    });
24229    assert_language_servers_count(
24230        1,
24231        "Second, external, *.rs file should join the existing server",
24232        cx,
24233    );
24234
24235    pane.update_in(cx, |pane, window, cx| {
24236        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24237    })
24238    .await
24239    .unwrap();
24240    pane.update_in(cx, |pane, window, cx| {
24241        pane.navigate_backward(&Default::default(), window, cx);
24242    });
24243    cx.run_until_parked();
24244    pane.update(cx, |pane, cx| {
24245        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24246        open_editor.update(cx, |editor, cx| {
24247            assert_eq!(
24248                editor.display_text(cx),
24249                "pub mod external {}",
24250                "External file is open now",
24251            );
24252        });
24253    });
24254    assert_language_servers_count(
24255        1,
24256        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
24257        cx,
24258    );
24259
24260    cx.update(|_, cx| {
24261        workspace::reload(cx);
24262    });
24263    assert_language_servers_count(
24264        1,
24265        "After reloading the worktree with local and external files opened, only one project should be started",
24266        cx,
24267    );
24268}
24269
24270#[gpui::test]
24271async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
24272    init_test(cx, |_| {});
24273
24274    let mut cx = EditorTestContext::new(cx).await;
24275    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24276    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24277
24278    // test cursor move to start of each line on tab
24279    // for `if`, `elif`, `else`, `while`, `with` and `for`
24280    cx.set_state(indoc! {"
24281        def main():
24282        ˇ    for item in items:
24283        ˇ        while item.active:
24284        ˇ            if item.value > 10:
24285        ˇ                continue
24286        ˇ            elif item.value < 0:
24287        ˇ                break
24288        ˇ            else:
24289        ˇ                with item.context() as ctx:
24290        ˇ                    yield count
24291        ˇ        else:
24292        ˇ            log('while else')
24293        ˇ    else:
24294        ˇ        log('for else')
24295    "});
24296    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24297    cx.assert_editor_state(indoc! {"
24298        def main():
24299            ˇfor item in items:
24300                ˇwhile item.active:
24301                    ˇif item.value > 10:
24302                        ˇcontinue
24303                    ˇelif item.value < 0:
24304                        ˇbreak
24305                    ˇelse:
24306                        ˇwith item.context() as ctx:
24307                            ˇyield count
24308                ˇelse:
24309                    ˇlog('while else')
24310            ˇelse:
24311                ˇlog('for else')
24312    "});
24313    // test relative indent is preserved when tab
24314    // for `if`, `elif`, `else`, `while`, `with` and `for`
24315    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24316    cx.assert_editor_state(indoc! {"
24317        def main():
24318                ˇfor item in items:
24319                    ˇwhile item.active:
24320                        ˇif item.value > 10:
24321                            ˇcontinue
24322                        ˇelif item.value < 0:
24323                            ˇbreak
24324                        ˇelse:
24325                            ˇwith item.context() as ctx:
24326                                ˇyield count
24327                    ˇelse:
24328                        ˇlog('while else')
24329                ˇelse:
24330                    ˇlog('for else')
24331    "});
24332
24333    // test cursor move to start of each line on tab
24334    // for `try`, `except`, `else`, `finally`, `match` and `def`
24335    cx.set_state(indoc! {"
24336        def main():
24337        ˇ    try:
24338        ˇ        fetch()
24339        ˇ    except ValueError:
24340        ˇ        handle_error()
24341        ˇ    else:
24342        ˇ        match value:
24343        ˇ            case _:
24344        ˇ    finally:
24345        ˇ        def status():
24346        ˇ            return 0
24347    "});
24348    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24349    cx.assert_editor_state(indoc! {"
24350        def main():
24351            ˇtry:
24352                ˇfetch()
24353            ˇexcept ValueError:
24354                ˇhandle_error()
24355            ˇelse:
24356                ˇmatch value:
24357                    ˇcase _:
24358            ˇfinally:
24359                ˇdef status():
24360                    ˇreturn 0
24361    "});
24362    // test relative indent is preserved when tab
24363    // for `try`, `except`, `else`, `finally`, `match` and `def`
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}
24379
24380#[gpui::test]
24381async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
24382    init_test(cx, |_| {});
24383
24384    let mut cx = EditorTestContext::new(cx).await;
24385    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24386    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24387
24388    // test `else` auto outdents when typed inside `if` block
24389    cx.set_state(indoc! {"
24390        def main():
24391            if i == 2:
24392                return
24393                ˇ
24394    "});
24395    cx.update_editor(|editor, window, cx| {
24396        editor.handle_input("else:", window, cx);
24397    });
24398    cx.assert_editor_state(indoc! {"
24399        def main():
24400            if i == 2:
24401                return
24402            else:ˇ
24403    "});
24404
24405    // test `except` auto outdents when typed inside `try` block
24406    cx.set_state(indoc! {"
24407        def main():
24408            try:
24409                i = 2
24410                ˇ
24411    "});
24412    cx.update_editor(|editor, window, cx| {
24413        editor.handle_input("except:", window, cx);
24414    });
24415    cx.assert_editor_state(indoc! {"
24416        def main():
24417            try:
24418                i = 2
24419            except:ˇ
24420    "});
24421
24422    // test `else` auto outdents when typed inside `except` block
24423    cx.set_state(indoc! {"
24424        def main():
24425            try:
24426                i = 2
24427            except:
24428                j = 2
24429                ˇ
24430    "});
24431    cx.update_editor(|editor, window, cx| {
24432        editor.handle_input("else:", window, cx);
24433    });
24434    cx.assert_editor_state(indoc! {"
24435        def main():
24436            try:
24437                i = 2
24438            except:
24439                j = 2
24440            else:ˇ
24441    "});
24442
24443    // test `finally` auto outdents when typed inside `else` block
24444    cx.set_state(indoc! {"
24445        def main():
24446            try:
24447                i = 2
24448            except:
24449                j = 2
24450            else:
24451                k = 2
24452                ˇ
24453    "});
24454    cx.update_editor(|editor, window, cx| {
24455        editor.handle_input("finally:", window, cx);
24456    });
24457    cx.assert_editor_state(indoc! {"
24458        def main():
24459            try:
24460                i = 2
24461            except:
24462                j = 2
24463            else:
24464                k = 2
24465            finally:ˇ
24466    "});
24467
24468    // test `else` does not outdents when typed inside `except` block right after for block
24469    cx.set_state(indoc! {"
24470        def main():
24471            try:
24472                i = 2
24473            except:
24474                for i in range(n):
24475                    pass
24476                ˇ
24477    "});
24478    cx.update_editor(|editor, window, cx| {
24479        editor.handle_input("else:", window, cx);
24480    });
24481    cx.assert_editor_state(indoc! {"
24482        def main():
24483            try:
24484                i = 2
24485            except:
24486                for i in range(n):
24487                    pass
24488                else:ˇ
24489    "});
24490
24491    // test `finally` auto outdents when typed inside `else` block right after for block
24492    cx.set_state(indoc! {"
24493        def main():
24494            try:
24495                i = 2
24496            except:
24497                j = 2
24498            else:
24499                for i in range(n):
24500                    pass
24501                ˇ
24502    "});
24503    cx.update_editor(|editor, window, cx| {
24504        editor.handle_input("finally:", window, cx);
24505    });
24506    cx.assert_editor_state(indoc! {"
24507        def main():
24508            try:
24509                i = 2
24510            except:
24511                j = 2
24512            else:
24513                for i in range(n):
24514                    pass
24515            finally:ˇ
24516    "});
24517
24518    // test `except` outdents to inner "try" block
24519    cx.set_state(indoc! {"
24520        def main():
24521            try:
24522                i = 2
24523                if i == 2:
24524                    try:
24525                        i = 3
24526                        ˇ
24527    "});
24528    cx.update_editor(|editor, window, cx| {
24529        editor.handle_input("except:", window, cx);
24530    });
24531    cx.assert_editor_state(indoc! {"
24532        def main():
24533            try:
24534                i = 2
24535                if i == 2:
24536                    try:
24537                        i = 3
24538                    except:ˇ
24539    "});
24540
24541    // test `except` outdents to outer "try" block
24542    cx.set_state(indoc! {"
24543        def main():
24544            try:
24545                i = 2
24546                if i == 2:
24547                    try:
24548                        i = 3
24549                ˇ
24550    "});
24551    cx.update_editor(|editor, window, cx| {
24552        editor.handle_input("except:", window, cx);
24553    });
24554    cx.assert_editor_state(indoc! {"
24555        def main():
24556            try:
24557                i = 2
24558                if i == 2:
24559                    try:
24560                        i = 3
24561            except:ˇ
24562    "});
24563
24564    // test `else` stays at correct indent when typed after `for` block
24565    cx.set_state(indoc! {"
24566        def main():
24567            for i in range(10):
24568                if i == 3:
24569                    break
24570            ˇ
24571    "});
24572    cx.update_editor(|editor, window, cx| {
24573        editor.handle_input("else:", window, cx);
24574    });
24575    cx.assert_editor_state(indoc! {"
24576        def main():
24577            for i in range(10):
24578                if i == 3:
24579                    break
24580            else:ˇ
24581    "});
24582
24583    // test does not outdent on typing after line with square brackets
24584    cx.set_state(indoc! {"
24585        def f() -> list[str]:
24586            ˇ
24587    "});
24588    cx.update_editor(|editor, window, cx| {
24589        editor.handle_input("a", window, cx);
24590    });
24591    cx.assert_editor_state(indoc! {"
24592        def f() -> list[str]:
2459324594    "});
24595
24596    // test does not outdent on typing : after case keyword
24597    cx.set_state(indoc! {"
24598        match 1:
24599            caseˇ
24600    "});
24601    cx.update_editor(|editor, window, cx| {
24602        editor.handle_input(":", window, cx);
24603    });
24604    cx.assert_editor_state(indoc! {"
24605        match 1:
24606            case:ˇ
24607    "});
24608}
24609
24610#[gpui::test]
24611async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
24612    init_test(cx, |_| {});
24613    update_test_language_settings(cx, |settings| {
24614        settings.defaults.extend_comment_on_newline = Some(false);
24615    });
24616    let mut cx = EditorTestContext::new(cx).await;
24617    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24618    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24619
24620    // test correct indent after newline on comment
24621    cx.set_state(indoc! {"
24622        # COMMENT:ˇ
24623    "});
24624    cx.update_editor(|editor, window, cx| {
24625        editor.newline(&Newline, window, cx);
24626    });
24627    cx.assert_editor_state(indoc! {"
24628        # COMMENT:
24629        ˇ
24630    "});
24631
24632    // test correct indent after newline in brackets
24633    cx.set_state(indoc! {"
24634        {ˇ}
24635    "});
24636    cx.update_editor(|editor, window, cx| {
24637        editor.newline(&Newline, window, cx);
24638    });
24639    cx.run_until_parked();
24640    cx.assert_editor_state(indoc! {"
24641        {
24642            ˇ
24643        }
24644    "});
24645
24646    cx.set_state(indoc! {"
24647        (ˇ)
24648    "});
24649    cx.update_editor(|editor, window, cx| {
24650        editor.newline(&Newline, window, cx);
24651    });
24652    cx.run_until_parked();
24653    cx.assert_editor_state(indoc! {"
24654        (
24655            ˇ
24656        )
24657    "});
24658
24659    // do not indent after empty lists or dictionaries
24660    cx.set_state(indoc! {"
24661        a = []ˇ
24662    "});
24663    cx.update_editor(|editor, window, cx| {
24664        editor.newline(&Newline, window, cx);
24665    });
24666    cx.run_until_parked();
24667    cx.assert_editor_state(indoc! {"
24668        a = []
24669        ˇ
24670    "});
24671}
24672
24673#[gpui::test]
24674async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
24675    init_test(cx, |_| {});
24676
24677    let mut cx = EditorTestContext::new(cx).await;
24678    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24679    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24680
24681    // test cursor move to start of each line on tab
24682    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
24683    cx.set_state(indoc! {"
24684        function main() {
24685        ˇ    for item in $items; do
24686        ˇ        while [ -n \"$item\" ]; do
24687        ˇ            if [ \"$value\" -gt 10 ]; then
24688        ˇ                continue
24689        ˇ            elif [ \"$value\" -lt 0 ]; then
24690        ˇ                break
24691        ˇ            else
24692        ˇ                echo \"$item\"
24693        ˇ            fi
24694        ˇ        done
24695        ˇ    done
24696        ˇ}
24697    "});
24698    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24699    cx.assert_editor_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    // test relative indent is preserved when tab
24715    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24716    cx.assert_editor_state(indoc! {"
24717        function main() {
24718                ˇfor item in $items; do
24719                    ˇwhile [ -n \"$item\" ]; do
24720                        ˇif [ \"$value\" -gt 10 ]; then
24721                            ˇcontinue
24722                        ˇelif [ \"$value\" -lt 0 ]; then
24723                            ˇbreak
24724                        ˇelse
24725                            ˇecho \"$item\"
24726                        ˇfi
24727                    ˇdone
24728                ˇdone
24729            ˇ}
24730    "});
24731
24732    // test cursor move to start of each line on tab
24733    // for `case` statement with patterns
24734    cx.set_state(indoc! {"
24735        function handle() {
24736        ˇ    case \"$1\" in
24737        ˇ        start)
24738        ˇ            echo \"a\"
24739        ˇ            ;;
24740        ˇ        stop)
24741        ˇ            echo \"b\"
24742        ˇ            ;;
24743        ˇ        *)
24744        ˇ            echo \"c\"
24745        ˇ            ;;
24746        ˇ    esac
24747        ˇ}
24748    "});
24749    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24750    cx.assert_editor_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}
24766
24767#[gpui::test]
24768async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
24769    init_test(cx, |_| {});
24770
24771    let mut cx = EditorTestContext::new(cx).await;
24772    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24773    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24774
24775    // test indents on comment insert
24776    cx.set_state(indoc! {"
24777        function main() {
24778        ˇ    for item in $items; do
24779        ˇ        while [ -n \"$item\" ]; do
24780        ˇ            if [ \"$value\" -gt 10 ]; then
24781        ˇ                continue
24782        ˇ            elif [ \"$value\" -lt 0 ]; then
24783        ˇ                break
24784        ˇ            else
24785        ˇ                echo \"$item\"
24786        ˇ            fi
24787        ˇ        done
24788        ˇ    done
24789        ˇ}
24790    "});
24791    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
24792    cx.assert_editor_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}
24808
24809#[gpui::test]
24810async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
24811    init_test(cx, |_| {});
24812
24813    let mut cx = EditorTestContext::new(cx).await;
24814    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24815    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24816
24817    // test `else` auto outdents when typed inside `if` block
24818    cx.set_state(indoc! {"
24819        if [ \"$1\" = \"test\" ]; then
24820            echo \"foo bar\"
24821            ˇ
24822    "});
24823    cx.update_editor(|editor, window, cx| {
24824        editor.handle_input("else", window, cx);
24825    });
24826    cx.assert_editor_state(indoc! {"
24827        if [ \"$1\" = \"test\" ]; then
24828            echo \"foo bar\"
24829        elseˇ
24830    "});
24831
24832    // test `elif` auto outdents when typed inside `if` block
24833    cx.set_state(indoc! {"
24834        if [ \"$1\" = \"test\" ]; then
24835            echo \"foo bar\"
24836            ˇ
24837    "});
24838    cx.update_editor(|editor, window, cx| {
24839        editor.handle_input("elif", window, cx);
24840    });
24841    cx.assert_editor_state(indoc! {"
24842        if [ \"$1\" = \"test\" ]; then
24843            echo \"foo bar\"
24844        elifˇ
24845    "});
24846
24847    // test `fi` auto outdents when typed inside `else` block
24848    cx.set_state(indoc! {"
24849        if [ \"$1\" = \"test\" ]; then
24850            echo \"foo bar\"
24851        else
24852            echo \"bar baz\"
24853            ˇ
24854    "});
24855    cx.update_editor(|editor, window, cx| {
24856        editor.handle_input("fi", window, cx);
24857    });
24858    cx.assert_editor_state(indoc! {"
24859        if [ \"$1\" = \"test\" ]; then
24860            echo \"foo bar\"
24861        else
24862            echo \"bar baz\"
24863        fiˇ
24864    "});
24865
24866    // test `done` auto outdents when typed inside `while` block
24867    cx.set_state(indoc! {"
24868        while read line; do
24869            echo \"$line\"
24870            ˇ
24871    "});
24872    cx.update_editor(|editor, window, cx| {
24873        editor.handle_input("done", window, cx);
24874    });
24875    cx.assert_editor_state(indoc! {"
24876        while read line; do
24877            echo \"$line\"
24878        doneˇ
24879    "});
24880
24881    // test `done` auto outdents when typed inside `for` block
24882    cx.set_state(indoc! {"
24883        for file in *.txt; do
24884            cat \"$file\"
24885            ˇ
24886    "});
24887    cx.update_editor(|editor, window, cx| {
24888        editor.handle_input("done", window, cx);
24889    });
24890    cx.assert_editor_state(indoc! {"
24891        for file in *.txt; do
24892            cat \"$file\"
24893        doneˇ
24894    "});
24895
24896    // test `esac` auto outdents when typed inside `case` block
24897    cx.set_state(indoc! {"
24898        case \"$1\" in
24899            start)
24900                echo \"foo bar\"
24901                ;;
24902            stop)
24903                echo \"bar baz\"
24904                ;;
24905            ˇ
24906    "});
24907    cx.update_editor(|editor, window, cx| {
24908        editor.handle_input("esac", window, cx);
24909    });
24910    cx.assert_editor_state(indoc! {"
24911        case \"$1\" in
24912            start)
24913                echo \"foo bar\"
24914                ;;
24915            stop)
24916                echo \"bar baz\"
24917                ;;
24918        esacˇ
24919    "});
24920
24921    // test `*)` auto outdents when typed inside `case` block
24922    cx.set_state(indoc! {"
24923        case \"$1\" in
24924            start)
24925                echo \"foo bar\"
24926                ;;
24927                ˇ
24928    "});
24929    cx.update_editor(|editor, window, cx| {
24930        editor.handle_input("*)", window, cx);
24931    });
24932    cx.assert_editor_state(indoc! {"
24933        case \"$1\" in
24934            start)
24935                echo \"foo bar\"
24936                ;;
24937            *)ˇ
24938    "});
24939
24940    // test `fi` outdents to correct level with nested if blocks
24941    cx.set_state(indoc! {"
24942        if [ \"$1\" = \"test\" ]; then
24943            echo \"outer if\"
24944            if [ \"$2\" = \"debug\" ]; then
24945                echo \"inner if\"
24946                ˇ
24947    "});
24948    cx.update_editor(|editor, window, cx| {
24949        editor.handle_input("fi", window, cx);
24950    });
24951    cx.assert_editor_state(indoc! {"
24952        if [ \"$1\" = \"test\" ]; then
24953            echo \"outer if\"
24954            if [ \"$2\" = \"debug\" ]; then
24955                echo \"inner if\"
24956            fiˇ
24957    "});
24958}
24959
24960#[gpui::test]
24961async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
24962    init_test(cx, |_| {});
24963    update_test_language_settings(cx, |settings| {
24964        settings.defaults.extend_comment_on_newline = Some(false);
24965    });
24966    let mut cx = EditorTestContext::new(cx).await;
24967    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24968    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24969
24970    // test correct indent after newline on comment
24971    cx.set_state(indoc! {"
24972        # COMMENT:ˇ
24973    "});
24974    cx.update_editor(|editor, window, cx| {
24975        editor.newline(&Newline, window, cx);
24976    });
24977    cx.assert_editor_state(indoc! {"
24978        # COMMENT:
24979        ˇ
24980    "});
24981
24982    // test correct indent after newline after `then`
24983    cx.set_state(indoc! {"
24984
24985        if [ \"$1\" = \"test\" ]; thenˇ
24986    "});
24987    cx.update_editor(|editor, window, cx| {
24988        editor.newline(&Newline, window, cx);
24989    });
24990    cx.run_until_parked();
24991    cx.assert_editor_state(indoc! {"
24992
24993        if [ \"$1\" = \"test\" ]; then
24994            ˇ
24995    "});
24996
24997    // test correct indent after newline after `else`
24998    cx.set_state(indoc! {"
24999        if [ \"$1\" = \"test\" ]; then
25000        elseˇ
25001    "});
25002    cx.update_editor(|editor, window, cx| {
25003        editor.newline(&Newline, window, cx);
25004    });
25005    cx.run_until_parked();
25006    cx.assert_editor_state(indoc! {"
25007        if [ \"$1\" = \"test\" ]; then
25008        else
25009            ˇ
25010    "});
25011
25012    // test correct indent after newline after `elif`
25013    cx.set_state(indoc! {"
25014        if [ \"$1\" = \"test\" ]; then
25015        elifˇ
25016    "});
25017    cx.update_editor(|editor, window, cx| {
25018        editor.newline(&Newline, window, cx);
25019    });
25020    cx.run_until_parked();
25021    cx.assert_editor_state(indoc! {"
25022        if [ \"$1\" = \"test\" ]; then
25023        elif
25024            ˇ
25025    "});
25026
25027    // test correct indent after newline after `do`
25028    cx.set_state(indoc! {"
25029        for file in *.txt; doˇ
25030    "});
25031    cx.update_editor(|editor, window, cx| {
25032        editor.newline(&Newline, window, cx);
25033    });
25034    cx.run_until_parked();
25035    cx.assert_editor_state(indoc! {"
25036        for file in *.txt; do
25037            ˇ
25038    "});
25039
25040    // test correct indent after newline after case pattern
25041    cx.set_state(indoc! {"
25042        case \"$1\" in
25043            start)ˇ
25044    "});
25045    cx.update_editor(|editor, window, cx| {
25046        editor.newline(&Newline, window, cx);
25047    });
25048    cx.run_until_parked();
25049    cx.assert_editor_state(indoc! {"
25050        case \"$1\" in
25051            start)
25052                ˇ
25053    "});
25054
25055    // test correct indent after newline after case pattern
25056    cx.set_state(indoc! {"
25057        case \"$1\" in
25058            start)
25059                ;;
25060            *)ˇ
25061    "});
25062    cx.update_editor(|editor, window, cx| {
25063        editor.newline(&Newline, window, cx);
25064    });
25065    cx.run_until_parked();
25066    cx.assert_editor_state(indoc! {"
25067        case \"$1\" in
25068            start)
25069                ;;
25070            *)
25071                ˇ
25072    "});
25073
25074    // test correct indent after newline after function opening brace
25075    cx.set_state(indoc! {"
25076        function test() {ˇ}
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        function test() {
25084            ˇ
25085        }
25086    "});
25087
25088    // test no extra indent after semicolon on same line
25089    cx.set_state(indoc! {"
25090        echo \"test\"25091    "});
25092    cx.update_editor(|editor, window, cx| {
25093        editor.newline(&Newline, window, cx);
25094    });
25095    cx.run_until_parked();
25096    cx.assert_editor_state(indoc! {"
25097        echo \"test\";
25098        ˇ
25099    "});
25100}
25101
25102fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
25103    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
25104    point..point
25105}
25106
25107#[track_caller]
25108fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
25109    let (text, ranges) = marked_text_ranges(marked_text, true);
25110    assert_eq!(editor.text(cx), text);
25111    assert_eq!(
25112        editor.selections.ranges(cx),
25113        ranges,
25114        "Assert selections are {}",
25115        marked_text
25116    );
25117}
25118
25119pub fn handle_signature_help_request(
25120    cx: &mut EditorLspTestContext,
25121    mocked_response: lsp::SignatureHelp,
25122) -> impl Future<Output = ()> + use<> {
25123    let mut request =
25124        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
25125            let mocked_response = mocked_response.clone();
25126            async move { Ok(Some(mocked_response)) }
25127        });
25128
25129    async move {
25130        request.next().await;
25131    }
25132}
25133
25134#[track_caller]
25135pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
25136    cx.update_editor(|editor, _, _| {
25137        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
25138            let entries = menu.entries.borrow();
25139            let entries = entries
25140                .iter()
25141                .map(|entry| entry.string.as_str())
25142                .collect::<Vec<_>>();
25143            assert_eq!(entries, expected);
25144        } else {
25145            panic!("Expected completions menu");
25146        }
25147    });
25148}
25149
25150/// Handle completion request passing a marked string specifying where the completion
25151/// should be triggered from using '|' character, what range should be replaced, and what completions
25152/// should be returned using '<' and '>' to delimit the range.
25153///
25154/// Also see `handle_completion_request_with_insert_and_replace`.
25155#[track_caller]
25156pub fn handle_completion_request(
25157    marked_string: &str,
25158    completions: Vec<&'static str>,
25159    is_incomplete: bool,
25160    counter: Arc<AtomicUsize>,
25161    cx: &mut EditorLspTestContext,
25162) -> impl Future<Output = ()> {
25163    let complete_from_marker: TextRangeMarker = '|'.into();
25164    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25165    let (_, mut marked_ranges) = marked_text_ranges_by(
25166        marked_string,
25167        vec![complete_from_marker.clone(), replace_range_marker.clone()],
25168    );
25169
25170    let complete_from_position =
25171        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25172    let replace_range =
25173        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25174
25175    let mut request =
25176        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25177            let completions = completions.clone();
25178            counter.fetch_add(1, atomic::Ordering::Release);
25179            async move {
25180                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25181                assert_eq!(
25182                    params.text_document_position.position,
25183                    complete_from_position
25184                );
25185                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
25186                    is_incomplete,
25187                    item_defaults: None,
25188                    items: completions
25189                        .iter()
25190                        .map(|completion_text| lsp::CompletionItem {
25191                            label: completion_text.to_string(),
25192                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
25193                                range: replace_range,
25194                                new_text: completion_text.to_string(),
25195                            })),
25196                            ..Default::default()
25197                        })
25198                        .collect(),
25199                })))
25200            }
25201        });
25202
25203    async move {
25204        request.next().await;
25205    }
25206}
25207
25208/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
25209/// given instead, which also contains an `insert` range.
25210///
25211/// This function uses markers to define ranges:
25212/// - `|` marks the cursor position
25213/// - `<>` marks the replace range
25214/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
25215pub fn handle_completion_request_with_insert_and_replace(
25216    cx: &mut EditorLspTestContext,
25217    marked_string: &str,
25218    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
25219    counter: Arc<AtomicUsize>,
25220) -> impl Future<Output = ()> {
25221    let complete_from_marker: TextRangeMarker = '|'.into();
25222    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25223    let insert_range_marker: TextRangeMarker = ('{', '}').into();
25224
25225    let (_, mut marked_ranges) = marked_text_ranges_by(
25226        marked_string,
25227        vec![
25228            complete_from_marker.clone(),
25229            replace_range_marker.clone(),
25230            insert_range_marker.clone(),
25231        ],
25232    );
25233
25234    let complete_from_position =
25235        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25236    let replace_range =
25237        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25238
25239    let insert_range = match marked_ranges.remove(&insert_range_marker) {
25240        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
25241        _ => lsp::Range {
25242            start: replace_range.start,
25243            end: complete_from_position,
25244        },
25245    };
25246
25247    let mut request =
25248        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25249            let completions = completions.clone();
25250            counter.fetch_add(1, atomic::Ordering::Release);
25251            async move {
25252                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25253                assert_eq!(
25254                    params.text_document_position.position, complete_from_position,
25255                    "marker `|` position doesn't match",
25256                );
25257                Ok(Some(lsp::CompletionResponse::Array(
25258                    completions
25259                        .iter()
25260                        .map(|(label, new_text)| lsp::CompletionItem {
25261                            label: label.to_string(),
25262                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25263                                lsp::InsertReplaceEdit {
25264                                    insert: insert_range,
25265                                    replace: replace_range,
25266                                    new_text: new_text.to_string(),
25267                                },
25268                            )),
25269                            ..Default::default()
25270                        })
25271                        .collect(),
25272                )))
25273            }
25274        });
25275
25276    async move {
25277        request.next().await;
25278    }
25279}
25280
25281fn handle_resolve_completion_request(
25282    cx: &mut EditorLspTestContext,
25283    edits: Option<Vec<(&'static str, &'static str)>>,
25284) -> impl Future<Output = ()> {
25285    let edits = edits.map(|edits| {
25286        edits
25287            .iter()
25288            .map(|(marked_string, new_text)| {
25289                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
25290                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
25291                lsp::TextEdit::new(replace_range, new_text.to_string())
25292            })
25293            .collect::<Vec<_>>()
25294    });
25295
25296    let mut request =
25297        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
25298            let edits = edits.clone();
25299            async move {
25300                Ok(lsp::CompletionItem {
25301                    additional_text_edits: edits,
25302                    ..Default::default()
25303                })
25304            }
25305        });
25306
25307    async move {
25308        request.next().await;
25309    }
25310}
25311
25312pub(crate) fn update_test_language_settings(
25313    cx: &mut TestAppContext,
25314    f: impl Fn(&mut AllLanguageSettingsContent),
25315) {
25316    cx.update(|cx| {
25317        SettingsStore::update_global(cx, |store, cx| {
25318            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
25319        });
25320    });
25321}
25322
25323pub(crate) fn update_test_project_settings(
25324    cx: &mut TestAppContext,
25325    f: impl Fn(&mut ProjectSettingsContent),
25326) {
25327    cx.update(|cx| {
25328        SettingsStore::update_global(cx, |store, cx| {
25329            store.update_user_settings(cx, |settings| f(&mut settings.project));
25330        });
25331    });
25332}
25333
25334pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
25335    cx.update(|cx| {
25336        assets::Assets.load_test_fonts(cx);
25337        let store = SettingsStore::test(cx);
25338        cx.set_global(store);
25339        theme::init(theme::LoadThemes::JustBase, cx);
25340        release_channel::init(SemanticVersion::default(), cx);
25341        client::init_settings(cx);
25342        language::init(cx);
25343        Project::init_settings(cx);
25344        workspace::init_settings(cx);
25345        crate::init(cx);
25346    });
25347    zlog::init_test();
25348    update_test_language_settings(cx, f);
25349}
25350
25351#[track_caller]
25352fn assert_hunk_revert(
25353    not_reverted_text_with_selections: &str,
25354    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
25355    expected_reverted_text_with_selections: &str,
25356    base_text: &str,
25357    cx: &mut EditorLspTestContext,
25358) {
25359    cx.set_state(not_reverted_text_with_selections);
25360    cx.set_head_text(base_text);
25361    cx.executor().run_until_parked();
25362
25363    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
25364        let snapshot = editor.snapshot(window, cx);
25365        let reverted_hunk_statuses = snapshot
25366            .buffer_snapshot
25367            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
25368            .map(|hunk| hunk.status().kind)
25369            .collect::<Vec<_>>();
25370
25371        editor.git_restore(&Default::default(), window, cx);
25372        reverted_hunk_statuses
25373    });
25374    cx.executor().run_until_parked();
25375    cx.assert_editor_state(expected_reverted_text_with_selections);
25376    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
25377}
25378
25379#[gpui::test(iterations = 10)]
25380async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
25381    init_test(cx, |_| {});
25382
25383    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
25384    let counter = diagnostic_requests.clone();
25385
25386    let fs = FakeFs::new(cx.executor());
25387    fs.insert_tree(
25388        path!("/a"),
25389        json!({
25390            "first.rs": "fn main() { let a = 5; }",
25391            "second.rs": "// Test file",
25392        }),
25393    )
25394    .await;
25395
25396    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25397    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25398    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25399
25400    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25401    language_registry.add(rust_lang());
25402    let mut fake_servers = language_registry.register_fake_lsp(
25403        "Rust",
25404        FakeLspAdapter {
25405            capabilities: lsp::ServerCapabilities {
25406                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
25407                    lsp::DiagnosticOptions {
25408                        identifier: None,
25409                        inter_file_dependencies: true,
25410                        workspace_diagnostics: true,
25411                        work_done_progress_options: Default::default(),
25412                    },
25413                )),
25414                ..Default::default()
25415            },
25416            ..Default::default()
25417        },
25418    );
25419
25420    let editor = workspace
25421        .update(cx, |workspace, window, cx| {
25422            workspace.open_abs_path(
25423                PathBuf::from(path!("/a/first.rs")),
25424                OpenOptions::default(),
25425                window,
25426                cx,
25427            )
25428        })
25429        .unwrap()
25430        .await
25431        .unwrap()
25432        .downcast::<Editor>()
25433        .unwrap();
25434    let fake_server = fake_servers.next().await.unwrap();
25435    let server_id = fake_server.server.server_id();
25436    let mut first_request = fake_server
25437        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
25438            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
25439            let result_id = Some(new_result_id.to_string());
25440            assert_eq!(
25441                params.text_document.uri,
25442                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25443            );
25444            async move {
25445                Ok(lsp::DocumentDiagnosticReportResult::Report(
25446                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
25447                        related_documents: None,
25448                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
25449                            items: Vec::new(),
25450                            result_id,
25451                        },
25452                    }),
25453                ))
25454            }
25455        });
25456
25457    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
25458        project.update(cx, |project, cx| {
25459            let buffer_id = editor
25460                .read(cx)
25461                .buffer()
25462                .read(cx)
25463                .as_singleton()
25464                .expect("created a singleton buffer")
25465                .read(cx)
25466                .remote_id();
25467            let buffer_result_id = project
25468                .lsp_store()
25469                .read(cx)
25470                .result_id(server_id, buffer_id, cx);
25471            assert_eq!(expected, buffer_result_id);
25472        });
25473    };
25474
25475    ensure_result_id(None, cx);
25476    cx.executor().advance_clock(Duration::from_millis(60));
25477    cx.executor().run_until_parked();
25478    assert_eq!(
25479        diagnostic_requests.load(atomic::Ordering::Acquire),
25480        1,
25481        "Opening file should trigger diagnostic request"
25482    );
25483    first_request
25484        .next()
25485        .await
25486        .expect("should have sent the first diagnostics pull request");
25487    ensure_result_id(Some("1".to_string()), cx);
25488
25489    // Editing should trigger diagnostics
25490    editor.update_in(cx, |editor, window, cx| {
25491        editor.handle_input("2", window, cx)
25492    });
25493    cx.executor().advance_clock(Duration::from_millis(60));
25494    cx.executor().run_until_parked();
25495    assert_eq!(
25496        diagnostic_requests.load(atomic::Ordering::Acquire),
25497        2,
25498        "Editing should trigger diagnostic request"
25499    );
25500    ensure_result_id(Some("2".to_string()), cx);
25501
25502    // Moving cursor should not trigger diagnostic request
25503    editor.update_in(cx, |editor, window, cx| {
25504        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25505            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
25506        });
25507    });
25508    cx.executor().advance_clock(Duration::from_millis(60));
25509    cx.executor().run_until_parked();
25510    assert_eq!(
25511        diagnostic_requests.load(atomic::Ordering::Acquire),
25512        2,
25513        "Cursor movement should not trigger diagnostic request"
25514    );
25515    ensure_result_id(Some("2".to_string()), cx);
25516    // Multiple rapid edits should be debounced
25517    for _ in 0..5 {
25518        editor.update_in(cx, |editor, window, cx| {
25519            editor.handle_input("x", window, cx)
25520        });
25521    }
25522    cx.executor().advance_clock(Duration::from_millis(60));
25523    cx.executor().run_until_parked();
25524
25525    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
25526    assert!(
25527        final_requests <= 4,
25528        "Multiple rapid edits should be debounced (got {final_requests} requests)",
25529    );
25530    ensure_result_id(Some(final_requests.to_string()), cx);
25531}
25532
25533#[gpui::test]
25534async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
25535    // Regression test for issue #11671
25536    // Previously, adding a cursor after moving multiple cursors would reset
25537    // the cursor count instead of adding to the existing cursors.
25538    init_test(cx, |_| {});
25539    let mut cx = EditorTestContext::new(cx).await;
25540
25541    // Create a simple buffer with cursor at start
25542    cx.set_state(indoc! {"
25543        ˇaaaa
25544        bbbb
25545        cccc
25546        dddd
25547        eeee
25548        ffff
25549        gggg
25550        hhhh"});
25551
25552    // Add 2 cursors below (so we have 3 total)
25553    cx.update_editor(|editor, window, cx| {
25554        editor.add_selection_below(&Default::default(), window, cx);
25555        editor.add_selection_below(&Default::default(), window, cx);
25556    });
25557
25558    // Verify we have 3 cursors
25559    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
25560    assert_eq!(
25561        initial_count, 3,
25562        "Should have 3 cursors after adding 2 below"
25563    );
25564
25565    // Move down one line
25566    cx.update_editor(|editor, window, cx| {
25567        editor.move_down(&MoveDown, window, cx);
25568    });
25569
25570    // Add another cursor below
25571    cx.update_editor(|editor, window, cx| {
25572        editor.add_selection_below(&Default::default(), window, cx);
25573    });
25574
25575    // Should now have 4 cursors (3 original + 1 new)
25576    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
25577    assert_eq!(
25578        final_count, 4,
25579        "Should have 4 cursors after moving and adding another"
25580    );
25581}
25582
25583#[gpui::test(iterations = 10)]
25584async fn test_document_colors(cx: &mut TestAppContext) {
25585    let expected_color = Rgba {
25586        r: 0.33,
25587        g: 0.33,
25588        b: 0.33,
25589        a: 0.33,
25590    };
25591
25592    init_test(cx, |_| {});
25593
25594    let fs = FakeFs::new(cx.executor());
25595    fs.insert_tree(
25596        path!("/a"),
25597        json!({
25598            "first.rs": "fn main() { let a = 5; }",
25599        }),
25600    )
25601    .await;
25602
25603    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25604    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25605    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25606
25607    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25608    language_registry.add(rust_lang());
25609    let mut fake_servers = language_registry.register_fake_lsp(
25610        "Rust",
25611        FakeLspAdapter {
25612            capabilities: lsp::ServerCapabilities {
25613                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
25614                ..lsp::ServerCapabilities::default()
25615            },
25616            name: "rust-analyzer",
25617            ..FakeLspAdapter::default()
25618        },
25619    );
25620    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
25621        "Rust",
25622        FakeLspAdapter {
25623            capabilities: lsp::ServerCapabilities {
25624                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
25625                ..lsp::ServerCapabilities::default()
25626            },
25627            name: "not-rust-analyzer",
25628            ..FakeLspAdapter::default()
25629        },
25630    );
25631
25632    let editor = workspace
25633        .update(cx, |workspace, window, cx| {
25634            workspace.open_abs_path(
25635                PathBuf::from(path!("/a/first.rs")),
25636                OpenOptions::default(),
25637                window,
25638                cx,
25639            )
25640        })
25641        .unwrap()
25642        .await
25643        .unwrap()
25644        .downcast::<Editor>()
25645        .unwrap();
25646    let fake_language_server = fake_servers.next().await.unwrap();
25647    let fake_language_server_without_capabilities =
25648        fake_servers_without_capabilities.next().await.unwrap();
25649    let requests_made = Arc::new(AtomicUsize::new(0));
25650    let closure_requests_made = Arc::clone(&requests_made);
25651    let mut color_request_handle = fake_language_server
25652        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25653            let requests_made = Arc::clone(&closure_requests_made);
25654            async move {
25655                assert_eq!(
25656                    params.text_document.uri,
25657                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25658                );
25659                requests_made.fetch_add(1, atomic::Ordering::Release);
25660                Ok(vec![
25661                    lsp::ColorInformation {
25662                        range: lsp::Range {
25663                            start: lsp::Position {
25664                                line: 0,
25665                                character: 0,
25666                            },
25667                            end: lsp::Position {
25668                                line: 0,
25669                                character: 1,
25670                            },
25671                        },
25672                        color: lsp::Color {
25673                            red: 0.33,
25674                            green: 0.33,
25675                            blue: 0.33,
25676                            alpha: 0.33,
25677                        },
25678                    },
25679                    lsp::ColorInformation {
25680                        range: lsp::Range {
25681                            start: lsp::Position {
25682                                line: 0,
25683                                character: 0,
25684                            },
25685                            end: lsp::Position {
25686                                line: 0,
25687                                character: 1,
25688                            },
25689                        },
25690                        color: lsp::Color {
25691                            red: 0.33,
25692                            green: 0.33,
25693                            blue: 0.33,
25694                            alpha: 0.33,
25695                        },
25696                    },
25697                ])
25698            }
25699        });
25700
25701    let _handle = fake_language_server_without_capabilities
25702        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
25703            panic!("Should not be called");
25704        });
25705    cx.executor().advance_clock(Duration::from_millis(100));
25706    color_request_handle.next().await.unwrap();
25707    cx.run_until_parked();
25708    assert_eq!(
25709        1,
25710        requests_made.load(atomic::Ordering::Acquire),
25711        "Should query for colors once per editor open"
25712    );
25713    editor.update_in(cx, |editor, _, cx| {
25714        assert_eq!(
25715            vec![expected_color],
25716            extract_color_inlays(editor, cx),
25717            "Should have an initial inlay"
25718        );
25719    });
25720
25721    // opening another file in a split should not influence the LSP query counter
25722    workspace
25723        .update(cx, |workspace, window, cx| {
25724            assert_eq!(
25725                workspace.panes().len(),
25726                1,
25727                "Should have one pane with one editor"
25728            );
25729            workspace.move_item_to_pane_in_direction(
25730                &MoveItemToPaneInDirection {
25731                    direction: SplitDirection::Right,
25732                    focus: false,
25733                    clone: true,
25734                },
25735                window,
25736                cx,
25737            );
25738        })
25739        .unwrap();
25740    cx.run_until_parked();
25741    workspace
25742        .update(cx, |workspace, _, cx| {
25743            let panes = workspace.panes();
25744            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
25745            for pane in panes {
25746                let editor = pane
25747                    .read(cx)
25748                    .active_item()
25749                    .and_then(|item| item.downcast::<Editor>())
25750                    .expect("Should have opened an editor in each split");
25751                let editor_file = editor
25752                    .read(cx)
25753                    .buffer()
25754                    .read(cx)
25755                    .as_singleton()
25756                    .expect("test deals with singleton buffers")
25757                    .read(cx)
25758                    .file()
25759                    .expect("test buffese should have a file")
25760                    .path();
25761                assert_eq!(
25762                    editor_file.as_ref(),
25763                    rel_path("first.rs"),
25764                    "Both editors should be opened for the same file"
25765                )
25766            }
25767        })
25768        .unwrap();
25769
25770    cx.executor().advance_clock(Duration::from_millis(500));
25771    let save = editor.update_in(cx, |editor, window, cx| {
25772        editor.move_to_end(&MoveToEnd, window, cx);
25773        editor.handle_input("dirty", window, cx);
25774        editor.save(
25775            SaveOptions {
25776                format: true,
25777                autosave: true,
25778            },
25779            project.clone(),
25780            window,
25781            cx,
25782        )
25783    });
25784    save.await.unwrap();
25785
25786    color_request_handle.next().await.unwrap();
25787    cx.run_until_parked();
25788    assert_eq!(
25789        3,
25790        requests_made.load(atomic::Ordering::Acquire),
25791        "Should query for colors once per save and once per formatting after save"
25792    );
25793
25794    drop(editor);
25795    let close = workspace
25796        .update(cx, |workspace, window, cx| {
25797            workspace.active_pane().update(cx, |pane, cx| {
25798                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25799            })
25800        })
25801        .unwrap();
25802    close.await.unwrap();
25803    let close = workspace
25804        .update(cx, |workspace, window, cx| {
25805            workspace.active_pane().update(cx, |pane, cx| {
25806                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25807            })
25808        })
25809        .unwrap();
25810    close.await.unwrap();
25811    assert_eq!(
25812        3,
25813        requests_made.load(atomic::Ordering::Acquire),
25814        "After saving and closing all editors, no extra requests should be made"
25815    );
25816    workspace
25817        .update(cx, |workspace, _, cx| {
25818            assert!(
25819                workspace.active_item(cx).is_none(),
25820                "Should close all editors"
25821            )
25822        })
25823        .unwrap();
25824
25825    workspace
25826        .update(cx, |workspace, window, cx| {
25827            workspace.active_pane().update(cx, |pane, cx| {
25828                pane.navigate_backward(&workspace::GoBack, window, cx);
25829            })
25830        })
25831        .unwrap();
25832    cx.executor().advance_clock(Duration::from_millis(100));
25833    cx.run_until_parked();
25834    let editor = workspace
25835        .update(cx, |workspace, _, cx| {
25836            workspace
25837                .active_item(cx)
25838                .expect("Should have reopened the editor again after navigating back")
25839                .downcast::<Editor>()
25840                .expect("Should be an editor")
25841        })
25842        .unwrap();
25843    color_request_handle.next().await.unwrap();
25844    assert_eq!(
25845        3,
25846        requests_made.load(atomic::Ordering::Acquire),
25847        "Cache should be reused on buffer close and reopen"
25848    );
25849    editor.update(cx, |editor, cx| {
25850        assert_eq!(
25851            vec![expected_color],
25852            extract_color_inlays(editor, cx),
25853            "Should have an initial inlay"
25854        );
25855    });
25856
25857    drop(color_request_handle);
25858    let closure_requests_made = Arc::clone(&requests_made);
25859    let mut empty_color_request_handle = fake_language_server
25860        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25861            let requests_made = Arc::clone(&closure_requests_made);
25862            async move {
25863                assert_eq!(
25864                    params.text_document.uri,
25865                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25866                );
25867                requests_made.fetch_add(1, atomic::Ordering::Release);
25868                Ok(Vec::new())
25869            }
25870        });
25871    let save = editor.update_in(cx, |editor, window, cx| {
25872        editor.move_to_end(&MoveToEnd, window, cx);
25873        editor.handle_input("dirty_again", window, cx);
25874        editor.save(
25875            SaveOptions {
25876                format: false,
25877                autosave: true,
25878            },
25879            project.clone(),
25880            window,
25881            cx,
25882        )
25883    });
25884    save.await.unwrap();
25885
25886    empty_color_request_handle.next().await.unwrap();
25887    cx.run_until_parked();
25888    assert_eq!(
25889        4,
25890        requests_made.load(atomic::Ordering::Acquire),
25891        "Should query for colors once per save only, as formatting was not requested"
25892    );
25893    editor.update(cx, |editor, cx| {
25894        assert_eq!(
25895            Vec::<Rgba>::new(),
25896            extract_color_inlays(editor, cx),
25897            "Should clear all colors when the server returns an empty response"
25898        );
25899    });
25900}
25901
25902#[gpui::test]
25903async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
25904    init_test(cx, |_| {});
25905    let (editor, cx) = cx.add_window_view(Editor::single_line);
25906    editor.update_in(cx, |editor, window, cx| {
25907        editor.set_text("oops\n\nwow\n", window, cx)
25908    });
25909    cx.run_until_parked();
25910    editor.update(cx, |editor, cx| {
25911        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
25912    });
25913    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
25914    cx.run_until_parked();
25915    editor.update(cx, |editor, cx| {
25916        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
25917    });
25918}
25919
25920#[gpui::test]
25921async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
25922    init_test(cx, |_| {});
25923
25924    cx.update(|cx| {
25925        register_project_item::<Editor>(cx);
25926    });
25927
25928    let fs = FakeFs::new(cx.executor());
25929    fs.insert_tree("/root1", json!({})).await;
25930    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
25931        .await;
25932
25933    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
25934    let (workspace, cx) =
25935        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25936
25937    let worktree_id = project.update(cx, |project, cx| {
25938        project.worktrees(cx).next().unwrap().read(cx).id()
25939    });
25940
25941    let handle = workspace
25942        .update_in(cx, |workspace, window, cx| {
25943            let project_path = (worktree_id, rel_path("one.pdf"));
25944            workspace.open_path(project_path, None, true, window, cx)
25945        })
25946        .await
25947        .unwrap();
25948
25949    assert_eq!(
25950        handle.to_any().entity_type(),
25951        TypeId::of::<InvalidBufferView>()
25952    );
25953}
25954
25955#[gpui::test]
25956async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
25957    init_test(cx, |_| {});
25958
25959    let language = Arc::new(Language::new(
25960        LanguageConfig::default(),
25961        Some(tree_sitter_rust::LANGUAGE.into()),
25962    ));
25963
25964    // Test hierarchical sibling navigation
25965    let text = r#"
25966        fn outer() {
25967            if condition {
25968                let a = 1;
25969            }
25970            let b = 2;
25971        }
25972
25973        fn another() {
25974            let c = 3;
25975        }
25976    "#;
25977
25978    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
25979    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25980    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
25981
25982    // Wait for parsing to complete
25983    editor
25984        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
25985        .await;
25986
25987    editor.update_in(cx, |editor, window, cx| {
25988        // Start by selecting "let a = 1;" inside the if block
25989        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25990            s.select_display_ranges([
25991                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
25992            ]);
25993        });
25994
25995        let initial_selection = editor.selections.display_ranges(cx);
25996        assert_eq!(initial_selection.len(), 1, "Should have one selection");
25997
25998        // Test select next sibling - should move up levels to find the next sibling
25999        // Since "let a = 1;" has no siblings in the if block, it should move up
26000        // to find "let b = 2;" which is a sibling of the if block
26001        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26002        let next_selection = editor.selections.display_ranges(cx);
26003
26004        // Should have a selection and it should be different from the initial
26005        assert_eq!(
26006            next_selection.len(),
26007            1,
26008            "Should have one selection after next"
26009        );
26010        assert_ne!(
26011            next_selection[0], initial_selection[0],
26012            "Next sibling selection should be different"
26013        );
26014
26015        // Test hierarchical navigation by going to the end of the current function
26016        // and trying to navigate to the next function
26017        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26018            s.select_display_ranges([
26019                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
26020            ]);
26021        });
26022
26023        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26024        let function_next_selection = editor.selections.display_ranges(cx);
26025
26026        // Should move to the next function
26027        assert_eq!(
26028            function_next_selection.len(),
26029            1,
26030            "Should have one selection after function next"
26031        );
26032
26033        // Test select previous sibling navigation
26034        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
26035        let prev_selection = editor.selections.display_ranges(cx);
26036
26037        // Should have a selection and it should be different
26038        assert_eq!(
26039            prev_selection.len(),
26040            1,
26041            "Should have one selection after prev"
26042        );
26043        assert_ne!(
26044            prev_selection[0], function_next_selection[0],
26045            "Previous sibling selection should be different from next"
26046        );
26047    });
26048}
26049
26050#[gpui::test]
26051async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
26052    init_test(cx, |_| {});
26053
26054    let mut cx = EditorTestContext::new(cx).await;
26055    cx.set_state(
26056        "let ˇvariable = 42;
26057let another = variable + 1;
26058let result = variable * 2;",
26059    );
26060
26061    // Set up document highlights manually (simulating LSP response)
26062    cx.update_editor(|editor, _window, cx| {
26063        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
26064
26065        // Create highlights for "variable" occurrences
26066        let highlight_ranges = [
26067            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
26068            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
26069            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
26070        ];
26071
26072        let anchor_ranges: Vec<_> = highlight_ranges
26073            .iter()
26074            .map(|range| range.clone().to_anchors(&buffer_snapshot))
26075            .collect();
26076
26077        editor.highlight_background::<DocumentHighlightRead>(
26078            &anchor_ranges,
26079            |theme| theme.colors().editor_document_highlight_read_background,
26080            cx,
26081        );
26082    });
26083
26084    // Go to next highlight - should move to second "variable"
26085    cx.update_editor(|editor, window, cx| {
26086        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26087    });
26088    cx.assert_editor_state(
26089        "let variable = 42;
26090let another = ˇvariable + 1;
26091let result = variable * 2;",
26092    );
26093
26094    // Go to next highlight - should move to third "variable"
26095    cx.update_editor(|editor, window, cx| {
26096        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26097    });
26098    cx.assert_editor_state(
26099        "let variable = 42;
26100let another = variable + 1;
26101let result = ˇvariable * 2;",
26102    );
26103
26104    // Go to next highlight - should stay at third "variable" (no wrap-around)
26105    cx.update_editor(|editor, window, cx| {
26106        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26107    });
26108    cx.assert_editor_state(
26109        "let variable = 42;
26110let another = variable + 1;
26111let result = ˇvariable * 2;",
26112    );
26113
26114    // Now test going backwards from third position
26115    cx.update_editor(|editor, window, cx| {
26116        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26117    });
26118    cx.assert_editor_state(
26119        "let variable = 42;
26120let another = ˇvariable + 1;
26121let result = variable * 2;",
26122    );
26123
26124    // Go to previous highlight - should move to first "variable"
26125    cx.update_editor(|editor, window, cx| {
26126        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26127    });
26128    cx.assert_editor_state(
26129        "let ˇvariable = 42;
26130let another = variable + 1;
26131let result = variable * 2;",
26132    );
26133
26134    // Go to previous highlight - should stay on first "variable"
26135    cx.update_editor(|editor, window, cx| {
26136        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26137    });
26138    cx.assert_editor_state(
26139        "let ˇvariable = 42;
26140let another = variable + 1;
26141let result = variable * 2;",
26142    );
26143}
26144
26145#[gpui::test]
26146async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
26147    cx: &mut gpui::TestAppContext,
26148) {
26149    init_test(cx, |_| {});
26150
26151    let url = "https://zed.dev";
26152
26153    let markdown_language = Arc::new(Language::new(
26154        LanguageConfig {
26155            name: "Markdown".into(),
26156            ..LanguageConfig::default()
26157        },
26158        None,
26159    ));
26160
26161    let mut cx = EditorTestContext::new(cx).await;
26162    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26163    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
26164
26165    cx.update_editor(|editor, window, cx| {
26166        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26167        editor.paste(&Paste, window, cx);
26168    });
26169
26170    cx.assert_editor_state(&format!(
26171        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
26172    ));
26173}
26174
26175#[gpui::test]
26176async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
26177    cx: &mut gpui::TestAppContext,
26178) {
26179    init_test(cx, |_| {});
26180
26181    let url = "https://zed.dev";
26182
26183    let markdown_language = Arc::new(Language::new(
26184        LanguageConfig {
26185            name: "Markdown".into(),
26186            ..LanguageConfig::default()
26187        },
26188        None,
26189    ));
26190
26191    let mut cx = EditorTestContext::new(cx).await;
26192    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26193    cx.set_state(&format!(
26194        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
26195    ));
26196
26197    cx.update_editor(|editor, window, cx| {
26198        editor.copy(&Copy, window, cx);
26199    });
26200
26201    cx.set_state(&format!(
26202        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
26203    ));
26204
26205    cx.update_editor(|editor, window, cx| {
26206        editor.paste(&Paste, window, cx);
26207    });
26208
26209    cx.assert_editor_state(&format!(
26210        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
26211    ));
26212}
26213
26214#[gpui::test]
26215async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
26216    cx: &mut gpui::TestAppContext,
26217) {
26218    init_test(cx, |_| {});
26219
26220    let url = "https://zed.dev";
26221
26222    let markdown_language = Arc::new(Language::new(
26223        LanguageConfig {
26224            name: "Markdown".into(),
26225            ..LanguageConfig::default()
26226        },
26227        None,
26228    ));
26229
26230    let mut cx = EditorTestContext::new(cx).await;
26231    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26232    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
26233
26234    cx.update_editor(|editor, window, cx| {
26235        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26236        editor.paste(&Paste, window, cx);
26237    });
26238
26239    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
26240}
26241
26242#[gpui::test]
26243async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
26244    cx: &mut gpui::TestAppContext,
26245) {
26246    init_test(cx, |_| {});
26247
26248    let text = "Awesome";
26249
26250    let markdown_language = Arc::new(Language::new(
26251        LanguageConfig {
26252            name: "Markdown".into(),
26253            ..LanguageConfig::default()
26254        },
26255        None,
26256    ));
26257
26258    let mut cx = EditorTestContext::new(cx).await;
26259    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26260    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
26261
26262    cx.update_editor(|editor, window, cx| {
26263        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
26264        editor.paste(&Paste, window, cx);
26265    });
26266
26267    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
26268}
26269
26270#[gpui::test]
26271async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
26272    cx: &mut gpui::TestAppContext,
26273) {
26274    init_test(cx, |_| {});
26275
26276    let url = "https://zed.dev";
26277
26278    let markdown_language = Arc::new(Language::new(
26279        LanguageConfig {
26280            name: "Rust".into(),
26281            ..LanguageConfig::default()
26282        },
26283        None,
26284    ));
26285
26286    let mut cx = EditorTestContext::new(cx).await;
26287    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26288    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
26289
26290    cx.update_editor(|editor, window, cx| {
26291        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26292        editor.paste(&Paste, window, cx);
26293    });
26294
26295    cx.assert_editor_state(&format!(
26296        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
26297    ));
26298}
26299
26300#[gpui::test]
26301async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
26302    cx: &mut TestAppContext,
26303) {
26304    init_test(cx, |_| {});
26305
26306    let url = "https://zed.dev";
26307
26308    let markdown_language = Arc::new(Language::new(
26309        LanguageConfig {
26310            name: "Markdown".into(),
26311            ..LanguageConfig::default()
26312        },
26313        None,
26314    ));
26315
26316    let (editor, cx) = cx.add_window_view(|window, cx| {
26317        let multi_buffer = MultiBuffer::build_multi(
26318            [
26319                ("this will embed -> link", vec![Point::row_range(0..1)]),
26320                ("this will replace -> link", vec![Point::row_range(0..1)]),
26321            ],
26322            cx,
26323        );
26324        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
26325        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26326            s.select_ranges(vec![
26327                Point::new(0, 19)..Point::new(0, 23),
26328                Point::new(1, 21)..Point::new(1, 25),
26329            ])
26330        });
26331        let first_buffer_id = multi_buffer
26332            .read(cx)
26333            .excerpt_buffer_ids()
26334            .into_iter()
26335            .next()
26336            .unwrap();
26337        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
26338        first_buffer.update(cx, |buffer, cx| {
26339            buffer.set_language(Some(markdown_language.clone()), cx);
26340        });
26341
26342        editor
26343    });
26344    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
26345
26346    cx.update_editor(|editor, window, cx| {
26347        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26348        editor.paste(&Paste, window, cx);
26349    });
26350
26351    cx.assert_editor_state(&format!(
26352        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
26353    ));
26354}
26355
26356#[gpui::test]
26357async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
26358    init_test(cx, |_| {});
26359
26360    let fs = FakeFs::new(cx.executor());
26361    fs.insert_tree(
26362        path!("/project"),
26363        json!({
26364            "first.rs": "# First Document\nSome content here.",
26365            "second.rs": "Plain text content for second file.",
26366        }),
26367    )
26368    .await;
26369
26370    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
26371    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26372    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26373
26374    let language = rust_lang();
26375    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26376    language_registry.add(language.clone());
26377    let mut fake_servers = language_registry.register_fake_lsp(
26378        "Rust",
26379        FakeLspAdapter {
26380            ..FakeLspAdapter::default()
26381        },
26382    );
26383
26384    let buffer1 = project
26385        .update(cx, |project, cx| {
26386            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
26387        })
26388        .await
26389        .unwrap();
26390    let buffer2 = project
26391        .update(cx, |project, cx| {
26392            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
26393        })
26394        .await
26395        .unwrap();
26396
26397    let multi_buffer = cx.new(|cx| {
26398        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
26399        multi_buffer.set_excerpts_for_path(
26400            PathKey::for_buffer(&buffer1, cx),
26401            buffer1.clone(),
26402            [Point::zero()..buffer1.read(cx).max_point()],
26403            3,
26404            cx,
26405        );
26406        multi_buffer.set_excerpts_for_path(
26407            PathKey::for_buffer(&buffer2, cx),
26408            buffer2.clone(),
26409            [Point::zero()..buffer1.read(cx).max_point()],
26410            3,
26411            cx,
26412        );
26413        multi_buffer
26414    });
26415
26416    let (editor, cx) = cx.add_window_view(|window, cx| {
26417        Editor::new(
26418            EditorMode::full(),
26419            multi_buffer,
26420            Some(project.clone()),
26421            window,
26422            cx,
26423        )
26424    });
26425
26426    let fake_language_server = fake_servers.next().await.unwrap();
26427
26428    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
26429
26430    let save = editor.update_in(cx, |editor, window, cx| {
26431        assert!(editor.is_dirty(cx));
26432
26433        editor.save(
26434            SaveOptions {
26435                format: true,
26436                autosave: true,
26437            },
26438            project,
26439            window,
26440            cx,
26441        )
26442    });
26443    let (start_edit_tx, start_edit_rx) = oneshot::channel();
26444    let (done_edit_tx, done_edit_rx) = oneshot::channel();
26445    let mut done_edit_rx = Some(done_edit_rx);
26446    let mut start_edit_tx = Some(start_edit_tx);
26447
26448    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
26449        start_edit_tx.take().unwrap().send(()).unwrap();
26450        let done_edit_rx = done_edit_rx.take().unwrap();
26451        async move {
26452            done_edit_rx.await.unwrap();
26453            Ok(None)
26454        }
26455    });
26456
26457    start_edit_rx.await.unwrap();
26458    buffer2
26459        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
26460        .unwrap();
26461
26462    done_edit_tx.send(()).unwrap();
26463
26464    save.await.unwrap();
26465    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
26466}
26467
26468#[track_caller]
26469fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
26470    editor
26471        .all_inlays(cx)
26472        .into_iter()
26473        .filter_map(|inlay| inlay.get_color())
26474        .map(Rgba::from)
26475        .collect()
26476}