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), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4304                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 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    // Record which buffer changes have been sent to the language server
12420    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12421    cx.lsp
12422        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12423            let buffer_changes = buffer_changes.clone();
12424            move |params, _| {
12425                buffer_changes.lock().extend(
12426                    params
12427                        .content_changes
12428                        .into_iter()
12429                        .map(|e| (e.range.unwrap(), e.text)),
12430                );
12431            }
12432        });
12433
12434    // Handle formatting requests to the language server.
12435    cx.lsp
12436        .set_request_handler::<lsp::request::Formatting, _, _>({
12437            let buffer_changes = buffer_changes.clone();
12438            move |_, _| {
12439                let buffer_changes = buffer_changes.clone();
12440                // Insert blank lines between each line of the buffer.
12441                async move {
12442                    // When formatting is requested, trailing whitespace has already been stripped,
12443                    // and the trailing newline has already been added.
12444                    assert_eq!(
12445                        &buffer_changes.lock()[1..],
12446                        &[
12447                            (
12448                                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12449                                "".into()
12450                            ),
12451                            (
12452                                lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12453                                "".into()
12454                            ),
12455                            (
12456                                lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12457                                "\n".into()
12458                            ),
12459                        ]
12460                    );
12461
12462                    Ok(Some(vec![
12463                        lsp::TextEdit {
12464                            range: lsp::Range::new(
12465                                lsp::Position::new(1, 0),
12466                                lsp::Position::new(1, 0),
12467                            ),
12468                            new_text: "\n".into(),
12469                        },
12470                        lsp::TextEdit {
12471                            range: lsp::Range::new(
12472                                lsp::Position::new(2, 0),
12473                                lsp::Position::new(2, 0),
12474                            ),
12475                            new_text: "\n".into(),
12476                        },
12477                    ]))
12478                }
12479            }
12480        });
12481
12482    // Submit a format request.
12483    let format = cx
12484        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12485        .unwrap();
12486
12487    cx.run_until_parked();
12488    // After formatting the buffer, the trailing whitespace is stripped,
12489    // a newline is appended, and the edits provided by the language server
12490    // have been applied.
12491    format.await.unwrap();
12492
12493    cx.assert_editor_state(
12494        &[
12495            "one",   //
12496            "",      //
12497            "twoˇ",  //
12498            "",      //
12499            "three", //
12500            "four",  //
12501            "",      //
12502        ]
12503        .join("\n"),
12504    );
12505
12506    // Undoing the formatting undoes the trailing whitespace removal, the
12507    // trailing newline, and the LSP edits.
12508    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12509    cx.assert_editor_state(
12510        &[
12511            "one ",   //
12512            "twoˇ",   //
12513            "three ", //
12514            "four",   //
12515        ]
12516        .join("\n"),
12517    );
12518}
12519
12520#[gpui::test]
12521async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12522    cx: &mut TestAppContext,
12523) {
12524    init_test(cx, |_| {});
12525
12526    cx.update(|cx| {
12527        cx.update_global::<SettingsStore, _>(|settings, cx| {
12528            settings.update_user_settings(cx, |settings| {
12529                settings.editor.auto_signature_help = Some(true);
12530            });
12531        });
12532    });
12533
12534    let mut cx = EditorLspTestContext::new_rust(
12535        lsp::ServerCapabilities {
12536            signature_help_provider: Some(lsp::SignatureHelpOptions {
12537                ..Default::default()
12538            }),
12539            ..Default::default()
12540        },
12541        cx,
12542    )
12543    .await;
12544
12545    let language = Language::new(
12546        LanguageConfig {
12547            name: "Rust".into(),
12548            brackets: BracketPairConfig {
12549                pairs: vec![
12550                    BracketPair {
12551                        start: "{".to_string(),
12552                        end: "}".to_string(),
12553                        close: true,
12554                        surround: true,
12555                        newline: true,
12556                    },
12557                    BracketPair {
12558                        start: "(".to_string(),
12559                        end: ")".to_string(),
12560                        close: true,
12561                        surround: true,
12562                        newline: true,
12563                    },
12564                    BracketPair {
12565                        start: "/*".to_string(),
12566                        end: " */".to_string(),
12567                        close: true,
12568                        surround: true,
12569                        newline: true,
12570                    },
12571                    BracketPair {
12572                        start: "[".to_string(),
12573                        end: "]".to_string(),
12574                        close: false,
12575                        surround: false,
12576                        newline: true,
12577                    },
12578                    BracketPair {
12579                        start: "\"".to_string(),
12580                        end: "\"".to_string(),
12581                        close: true,
12582                        surround: true,
12583                        newline: false,
12584                    },
12585                    BracketPair {
12586                        start: "<".to_string(),
12587                        end: ">".to_string(),
12588                        close: false,
12589                        surround: true,
12590                        newline: true,
12591                    },
12592                ],
12593                ..Default::default()
12594            },
12595            autoclose_before: "})]".to_string(),
12596            ..Default::default()
12597        },
12598        Some(tree_sitter_rust::LANGUAGE.into()),
12599    );
12600    let language = Arc::new(language);
12601
12602    cx.language_registry().add(language.clone());
12603    cx.update_buffer(|buffer, cx| {
12604        buffer.set_language(Some(language), cx);
12605    });
12606
12607    cx.set_state(
12608        &r#"
12609            fn main() {
12610                sampleˇ
12611            }
12612        "#
12613        .unindent(),
12614    );
12615
12616    cx.update_editor(|editor, window, cx| {
12617        editor.handle_input("(", window, cx);
12618    });
12619    cx.assert_editor_state(
12620        &"
12621            fn main() {
12622                sample(ˇ)
12623            }
12624        "
12625        .unindent(),
12626    );
12627
12628    let mocked_response = lsp::SignatureHelp {
12629        signatures: vec![lsp::SignatureInformation {
12630            label: "fn sample(param1: u8, param2: u8)".to_string(),
12631            documentation: None,
12632            parameters: Some(vec![
12633                lsp::ParameterInformation {
12634                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12635                    documentation: None,
12636                },
12637                lsp::ParameterInformation {
12638                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12639                    documentation: None,
12640                },
12641            ]),
12642            active_parameter: None,
12643        }],
12644        active_signature: Some(0),
12645        active_parameter: Some(0),
12646    };
12647    handle_signature_help_request(&mut cx, mocked_response).await;
12648
12649    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12650        .await;
12651
12652    cx.editor(|editor, _, _| {
12653        let signature_help_state = editor.signature_help_state.popover().cloned();
12654        let signature = signature_help_state.unwrap();
12655        assert_eq!(
12656            signature.signatures[signature.current_signature].label,
12657            "fn sample(param1: u8, param2: u8)"
12658        );
12659    });
12660}
12661
12662#[gpui::test]
12663async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
12664    init_test(cx, |_| {});
12665
12666    cx.update(|cx| {
12667        cx.update_global::<SettingsStore, _>(|settings, cx| {
12668            settings.update_user_settings(cx, |settings| {
12669                settings.editor.auto_signature_help = Some(false);
12670                settings.editor.show_signature_help_after_edits = Some(false);
12671            });
12672        });
12673    });
12674
12675    let mut cx = EditorLspTestContext::new_rust(
12676        lsp::ServerCapabilities {
12677            signature_help_provider: Some(lsp::SignatureHelpOptions {
12678                ..Default::default()
12679            }),
12680            ..Default::default()
12681        },
12682        cx,
12683    )
12684    .await;
12685
12686    let language = Language::new(
12687        LanguageConfig {
12688            name: "Rust".into(),
12689            brackets: BracketPairConfig {
12690                pairs: vec![
12691                    BracketPair {
12692                        start: "{".to_string(),
12693                        end: "}".to_string(),
12694                        close: true,
12695                        surround: true,
12696                        newline: true,
12697                    },
12698                    BracketPair {
12699                        start: "(".to_string(),
12700                        end: ")".to_string(),
12701                        close: true,
12702                        surround: true,
12703                        newline: true,
12704                    },
12705                    BracketPair {
12706                        start: "/*".to_string(),
12707                        end: " */".to_string(),
12708                        close: true,
12709                        surround: true,
12710                        newline: true,
12711                    },
12712                    BracketPair {
12713                        start: "[".to_string(),
12714                        end: "]".to_string(),
12715                        close: false,
12716                        surround: false,
12717                        newline: true,
12718                    },
12719                    BracketPair {
12720                        start: "\"".to_string(),
12721                        end: "\"".to_string(),
12722                        close: true,
12723                        surround: true,
12724                        newline: false,
12725                    },
12726                    BracketPair {
12727                        start: "<".to_string(),
12728                        end: ">".to_string(),
12729                        close: false,
12730                        surround: true,
12731                        newline: true,
12732                    },
12733                ],
12734                ..Default::default()
12735            },
12736            autoclose_before: "})]".to_string(),
12737            ..Default::default()
12738        },
12739        Some(tree_sitter_rust::LANGUAGE.into()),
12740    );
12741    let language = Arc::new(language);
12742
12743    cx.language_registry().add(language.clone());
12744    cx.update_buffer(|buffer, cx| {
12745        buffer.set_language(Some(language), cx);
12746    });
12747
12748    // Ensure that signature_help is not called when no signature help is enabled.
12749    cx.set_state(
12750        &r#"
12751            fn main() {
12752                sampleˇ
12753            }
12754        "#
12755        .unindent(),
12756    );
12757    cx.update_editor(|editor, window, cx| {
12758        editor.handle_input("(", window, cx);
12759    });
12760    cx.assert_editor_state(
12761        &"
12762            fn main() {
12763                sample(ˇ)
12764            }
12765        "
12766        .unindent(),
12767    );
12768    cx.editor(|editor, _, _| {
12769        assert!(editor.signature_help_state.task().is_none());
12770    });
12771
12772    let mocked_response = lsp::SignatureHelp {
12773        signatures: vec![lsp::SignatureInformation {
12774            label: "fn sample(param1: u8, param2: u8)".to_string(),
12775            documentation: None,
12776            parameters: Some(vec![
12777                lsp::ParameterInformation {
12778                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12779                    documentation: None,
12780                },
12781                lsp::ParameterInformation {
12782                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12783                    documentation: None,
12784                },
12785            ]),
12786            active_parameter: None,
12787        }],
12788        active_signature: Some(0),
12789        active_parameter: Some(0),
12790    };
12791
12792    // Ensure that signature_help is called when enabled afte edits
12793    cx.update(|_, cx| {
12794        cx.update_global::<SettingsStore, _>(|settings, cx| {
12795            settings.update_user_settings(cx, |settings| {
12796                settings.editor.auto_signature_help = Some(false);
12797                settings.editor.show_signature_help_after_edits = Some(true);
12798            });
12799        });
12800    });
12801    cx.set_state(
12802        &r#"
12803            fn main() {
12804                sampleˇ
12805            }
12806        "#
12807        .unindent(),
12808    );
12809    cx.update_editor(|editor, window, cx| {
12810        editor.handle_input("(", window, cx);
12811    });
12812    cx.assert_editor_state(
12813        &"
12814            fn main() {
12815                sample(ˇ)
12816            }
12817        "
12818        .unindent(),
12819    );
12820    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12821    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12822        .await;
12823    cx.update_editor(|editor, _, _| {
12824        let signature_help_state = editor.signature_help_state.popover().cloned();
12825        assert!(signature_help_state.is_some());
12826        let signature = signature_help_state.unwrap();
12827        assert_eq!(
12828            signature.signatures[signature.current_signature].label,
12829            "fn sample(param1: u8, param2: u8)"
12830        );
12831        editor.signature_help_state = SignatureHelpState::default();
12832    });
12833
12834    // Ensure that signature_help is called when auto signature help override is enabled
12835    cx.update(|_, cx| {
12836        cx.update_global::<SettingsStore, _>(|settings, cx| {
12837            settings.update_user_settings(cx, |settings| {
12838                settings.editor.auto_signature_help = Some(true);
12839                settings.editor.show_signature_help_after_edits = Some(false);
12840            });
12841        });
12842    });
12843    cx.set_state(
12844        &r#"
12845            fn main() {
12846                sampleˇ
12847            }
12848        "#
12849        .unindent(),
12850    );
12851    cx.update_editor(|editor, window, cx| {
12852        editor.handle_input("(", window, cx);
12853    });
12854    cx.assert_editor_state(
12855        &"
12856            fn main() {
12857                sample(ˇ)
12858            }
12859        "
12860        .unindent(),
12861    );
12862    handle_signature_help_request(&mut cx, mocked_response).await;
12863    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12864        .await;
12865    cx.editor(|editor, _, _| {
12866        let signature_help_state = editor.signature_help_state.popover().cloned();
12867        assert!(signature_help_state.is_some());
12868        let signature = signature_help_state.unwrap();
12869        assert_eq!(
12870            signature.signatures[signature.current_signature].label,
12871            "fn sample(param1: u8, param2: u8)"
12872        );
12873    });
12874}
12875
12876#[gpui::test]
12877async fn test_signature_help(cx: &mut TestAppContext) {
12878    init_test(cx, |_| {});
12879    cx.update(|cx| {
12880        cx.update_global::<SettingsStore, _>(|settings, cx| {
12881            settings.update_user_settings(cx, |settings| {
12882                settings.editor.auto_signature_help = Some(true);
12883            });
12884        });
12885    });
12886
12887    let mut cx = EditorLspTestContext::new_rust(
12888        lsp::ServerCapabilities {
12889            signature_help_provider: Some(lsp::SignatureHelpOptions {
12890                ..Default::default()
12891            }),
12892            ..Default::default()
12893        },
12894        cx,
12895    )
12896    .await;
12897
12898    // A test that directly calls `show_signature_help`
12899    cx.update_editor(|editor, window, cx| {
12900        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12901    });
12902
12903    let mocked_response = lsp::SignatureHelp {
12904        signatures: vec![lsp::SignatureInformation {
12905            label: "fn sample(param1: u8, param2: u8)".to_string(),
12906            documentation: None,
12907            parameters: Some(vec![
12908                lsp::ParameterInformation {
12909                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12910                    documentation: None,
12911                },
12912                lsp::ParameterInformation {
12913                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12914                    documentation: None,
12915                },
12916            ]),
12917            active_parameter: None,
12918        }],
12919        active_signature: Some(0),
12920        active_parameter: Some(0),
12921    };
12922    handle_signature_help_request(&mut cx, mocked_response).await;
12923
12924    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12925        .await;
12926
12927    cx.editor(|editor, _, _| {
12928        let signature_help_state = editor.signature_help_state.popover().cloned();
12929        assert!(signature_help_state.is_some());
12930        let signature = signature_help_state.unwrap();
12931        assert_eq!(
12932            signature.signatures[signature.current_signature].label,
12933            "fn sample(param1: u8, param2: u8)"
12934        );
12935    });
12936
12937    // When exiting outside from inside the brackets, `signature_help` is closed.
12938    cx.set_state(indoc! {"
12939        fn main() {
12940            sample(ˇ);
12941        }
12942
12943        fn sample(param1: u8, param2: u8) {}
12944    "});
12945
12946    cx.update_editor(|editor, window, cx| {
12947        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12948            s.select_ranges([0..0])
12949        });
12950    });
12951
12952    let mocked_response = lsp::SignatureHelp {
12953        signatures: Vec::new(),
12954        active_signature: None,
12955        active_parameter: None,
12956    };
12957    handle_signature_help_request(&mut cx, mocked_response).await;
12958
12959    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
12960        .await;
12961
12962    cx.editor(|editor, _, _| {
12963        assert!(!editor.signature_help_state.is_shown());
12964    });
12965
12966    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
12967    cx.set_state(indoc! {"
12968        fn main() {
12969            sample(ˇ);
12970        }
12971
12972        fn sample(param1: u8, param2: u8) {}
12973    "});
12974
12975    let mocked_response = lsp::SignatureHelp {
12976        signatures: vec![lsp::SignatureInformation {
12977            label: "fn sample(param1: u8, param2: u8)".to_string(),
12978            documentation: None,
12979            parameters: Some(vec![
12980                lsp::ParameterInformation {
12981                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12982                    documentation: None,
12983                },
12984                lsp::ParameterInformation {
12985                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12986                    documentation: None,
12987                },
12988            ]),
12989            active_parameter: None,
12990        }],
12991        active_signature: Some(0),
12992        active_parameter: Some(0),
12993    };
12994    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12995    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12996        .await;
12997    cx.editor(|editor, _, _| {
12998        assert!(editor.signature_help_state.is_shown());
12999    });
13000
13001    // Restore the popover with more parameter input
13002    cx.set_state(indoc! {"
13003        fn main() {
13004            sample(param1, param2ˇ);
13005        }
13006
13007        fn sample(param1: u8, param2: u8) {}
13008    "});
13009
13010    let mocked_response = lsp::SignatureHelp {
13011        signatures: vec![lsp::SignatureInformation {
13012            label: "fn sample(param1: u8, param2: u8)".to_string(),
13013            documentation: None,
13014            parameters: Some(vec![
13015                lsp::ParameterInformation {
13016                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13017                    documentation: None,
13018                },
13019                lsp::ParameterInformation {
13020                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13021                    documentation: None,
13022                },
13023            ]),
13024            active_parameter: None,
13025        }],
13026        active_signature: Some(0),
13027        active_parameter: Some(1),
13028    };
13029    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13030    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13031        .await;
13032
13033    // When selecting a range, the popover is gone.
13034    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
13035    cx.update_editor(|editor, window, cx| {
13036        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13037            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13038        })
13039    });
13040    cx.assert_editor_state(indoc! {"
13041        fn main() {
13042            sample(param1, «ˇparam2»);
13043        }
13044
13045        fn sample(param1: u8, param2: u8) {}
13046    "});
13047    cx.editor(|editor, _, _| {
13048        assert!(!editor.signature_help_state.is_shown());
13049    });
13050
13051    // When unselecting again, the popover is back if within the brackets.
13052    cx.update_editor(|editor, window, cx| {
13053        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13054            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13055        })
13056    });
13057    cx.assert_editor_state(indoc! {"
13058        fn main() {
13059            sample(param1, ˇparam2);
13060        }
13061
13062        fn sample(param1: u8, param2: u8) {}
13063    "});
13064    handle_signature_help_request(&mut cx, mocked_response).await;
13065    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13066        .await;
13067    cx.editor(|editor, _, _| {
13068        assert!(editor.signature_help_state.is_shown());
13069    });
13070
13071    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13072    cx.update_editor(|editor, window, cx| {
13073        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13074            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13075            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13076        })
13077    });
13078    cx.assert_editor_state(indoc! {"
13079        fn main() {
13080            sample(param1, ˇparam2);
13081        }
13082
13083        fn sample(param1: u8, param2: u8) {}
13084    "});
13085
13086    let mocked_response = lsp::SignatureHelp {
13087        signatures: vec![lsp::SignatureInformation {
13088            label: "fn sample(param1: u8, param2: u8)".to_string(),
13089            documentation: None,
13090            parameters: Some(vec![
13091                lsp::ParameterInformation {
13092                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13093                    documentation: None,
13094                },
13095                lsp::ParameterInformation {
13096                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13097                    documentation: None,
13098                },
13099            ]),
13100            active_parameter: None,
13101        }],
13102        active_signature: Some(0),
13103        active_parameter: Some(1),
13104    };
13105    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13106    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13107        .await;
13108    cx.update_editor(|editor, _, cx| {
13109        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13110    });
13111    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13112        .await;
13113    cx.update_editor(|editor, window, cx| {
13114        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13115            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13116        })
13117    });
13118    cx.assert_editor_state(indoc! {"
13119        fn main() {
13120            sample(param1, «ˇparam2»);
13121        }
13122
13123        fn sample(param1: u8, param2: u8) {}
13124    "});
13125    cx.update_editor(|editor, window, cx| {
13126        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13127            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13128        })
13129    });
13130    cx.assert_editor_state(indoc! {"
13131        fn main() {
13132            sample(param1, ˇparam2);
13133        }
13134
13135        fn sample(param1: u8, param2: u8) {}
13136    "});
13137    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13138        .await;
13139}
13140
13141#[gpui::test]
13142async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13143    init_test(cx, |_| {});
13144
13145    let mut cx = EditorLspTestContext::new_rust(
13146        lsp::ServerCapabilities {
13147            signature_help_provider: Some(lsp::SignatureHelpOptions {
13148                ..Default::default()
13149            }),
13150            ..Default::default()
13151        },
13152        cx,
13153    )
13154    .await;
13155
13156    cx.set_state(indoc! {"
13157        fn main() {
13158            overloadedˇ
13159        }
13160    "});
13161
13162    cx.update_editor(|editor, window, cx| {
13163        editor.handle_input("(", window, cx);
13164        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13165    });
13166
13167    // Mock response with 3 signatures
13168    let mocked_response = lsp::SignatureHelp {
13169        signatures: vec![
13170            lsp::SignatureInformation {
13171                label: "fn overloaded(x: i32)".to_string(),
13172                documentation: None,
13173                parameters: Some(vec![lsp::ParameterInformation {
13174                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13175                    documentation: None,
13176                }]),
13177                active_parameter: None,
13178            },
13179            lsp::SignatureInformation {
13180                label: "fn overloaded(x: i32, y: i32)".to_string(),
13181                documentation: None,
13182                parameters: Some(vec![
13183                    lsp::ParameterInformation {
13184                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13185                        documentation: None,
13186                    },
13187                    lsp::ParameterInformation {
13188                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13189                        documentation: None,
13190                    },
13191                ]),
13192                active_parameter: None,
13193            },
13194            lsp::SignatureInformation {
13195                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13196                documentation: None,
13197                parameters: Some(vec![
13198                    lsp::ParameterInformation {
13199                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13200                        documentation: None,
13201                    },
13202                    lsp::ParameterInformation {
13203                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13204                        documentation: None,
13205                    },
13206                    lsp::ParameterInformation {
13207                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13208                        documentation: None,
13209                    },
13210                ]),
13211                active_parameter: None,
13212            },
13213        ],
13214        active_signature: Some(1),
13215        active_parameter: Some(0),
13216    };
13217    handle_signature_help_request(&mut cx, mocked_response).await;
13218
13219    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13220        .await;
13221
13222    // Verify we have multiple signatures and the right one is selected
13223    cx.editor(|editor, _, _| {
13224        let popover = editor.signature_help_state.popover().cloned().unwrap();
13225        assert_eq!(popover.signatures.len(), 3);
13226        // active_signature was 1, so that should be the current
13227        assert_eq!(popover.current_signature, 1);
13228        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13229        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13230        assert_eq!(
13231            popover.signatures[2].label,
13232            "fn overloaded(x: i32, y: i32, z: i32)"
13233        );
13234    });
13235
13236    // Test navigation functionality
13237    cx.update_editor(|editor, window, cx| {
13238        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13239    });
13240
13241    cx.editor(|editor, _, _| {
13242        let popover = editor.signature_help_state.popover().cloned().unwrap();
13243        assert_eq!(popover.current_signature, 2);
13244    });
13245
13246    // Test wrap around
13247    cx.update_editor(|editor, window, cx| {
13248        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13249    });
13250
13251    cx.editor(|editor, _, _| {
13252        let popover = editor.signature_help_state.popover().cloned().unwrap();
13253        assert_eq!(popover.current_signature, 0);
13254    });
13255
13256    // Test previous navigation
13257    cx.update_editor(|editor, window, cx| {
13258        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13259    });
13260
13261    cx.editor(|editor, _, _| {
13262        let popover = editor.signature_help_state.popover().cloned().unwrap();
13263        assert_eq!(popover.current_signature, 2);
13264    });
13265}
13266
13267#[gpui::test]
13268async fn test_completion_mode(cx: &mut TestAppContext) {
13269    init_test(cx, |_| {});
13270    let mut cx = EditorLspTestContext::new_rust(
13271        lsp::ServerCapabilities {
13272            completion_provider: Some(lsp::CompletionOptions {
13273                resolve_provider: Some(true),
13274                ..Default::default()
13275            }),
13276            ..Default::default()
13277        },
13278        cx,
13279    )
13280    .await;
13281
13282    struct Run {
13283        run_description: &'static str,
13284        initial_state: String,
13285        buffer_marked_text: String,
13286        completion_label: &'static str,
13287        completion_text: &'static str,
13288        expected_with_insert_mode: String,
13289        expected_with_replace_mode: String,
13290        expected_with_replace_subsequence_mode: String,
13291        expected_with_replace_suffix_mode: String,
13292    }
13293
13294    let runs = [
13295        Run {
13296            run_description: "Start of word matches completion text",
13297            initial_state: "before ediˇ after".into(),
13298            buffer_marked_text: "before <edi|> after".into(),
13299            completion_label: "editor",
13300            completion_text: "editor",
13301            expected_with_insert_mode: "before editorˇ after".into(),
13302            expected_with_replace_mode: "before editorˇ after".into(),
13303            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13304            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13305        },
13306        Run {
13307            run_description: "Accept same text at the middle of the word",
13308            initial_state: "before ediˇtor after".into(),
13309            buffer_marked_text: "before <edi|tor> after".into(),
13310            completion_label: "editor",
13311            completion_text: "editor",
13312            expected_with_insert_mode: "before editorˇtor after".into(),
13313            expected_with_replace_mode: "before editorˇ after".into(),
13314            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13315            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13316        },
13317        Run {
13318            run_description: "End of word matches completion text -- cursor at end",
13319            initial_state: "before torˇ after".into(),
13320            buffer_marked_text: "before <tor|> after".into(),
13321            completion_label: "editor",
13322            completion_text: "editor",
13323            expected_with_insert_mode: "before editorˇ after".into(),
13324            expected_with_replace_mode: "before editorˇ after".into(),
13325            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13326            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13327        },
13328        Run {
13329            run_description: "End of word matches completion text -- cursor at start",
13330            initial_state: "before ˇtor after".into(),
13331            buffer_marked_text: "before <|tor> after".into(),
13332            completion_label: "editor",
13333            completion_text: "editor",
13334            expected_with_insert_mode: "before editorˇtor after".into(),
13335            expected_with_replace_mode: "before editorˇ after".into(),
13336            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13337            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13338        },
13339        Run {
13340            run_description: "Prepend text containing whitespace",
13341            initial_state: "pˇfield: bool".into(),
13342            buffer_marked_text: "<p|field>: bool".into(),
13343            completion_label: "pub ",
13344            completion_text: "pub ",
13345            expected_with_insert_mode: "pub ˇfield: bool".into(),
13346            expected_with_replace_mode: "pub ˇ: bool".into(),
13347            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13348            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13349        },
13350        Run {
13351            run_description: "Add element to start of list",
13352            initial_state: "[element_ˇelement_2]".into(),
13353            buffer_marked_text: "[<element_|element_2>]".into(),
13354            completion_label: "element_1",
13355            completion_text: "element_1",
13356            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13357            expected_with_replace_mode: "[element_1ˇ]".into(),
13358            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13359            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13360        },
13361        Run {
13362            run_description: "Add element to start of list -- first and second elements are equal",
13363            initial_state: "[elˇelement]".into(),
13364            buffer_marked_text: "[<el|element>]".into(),
13365            completion_label: "element",
13366            completion_text: "element",
13367            expected_with_insert_mode: "[elementˇelement]".into(),
13368            expected_with_replace_mode: "[elementˇ]".into(),
13369            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13370            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13371        },
13372        Run {
13373            run_description: "Ends with matching suffix",
13374            initial_state: "SubˇError".into(),
13375            buffer_marked_text: "<Sub|Error>".into(),
13376            completion_label: "SubscriptionError",
13377            completion_text: "SubscriptionError",
13378            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13379            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13380            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13381            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13382        },
13383        Run {
13384            run_description: "Suffix is a subsequence -- contiguous",
13385            initial_state: "SubˇErr".into(),
13386            buffer_marked_text: "<Sub|Err>".into(),
13387            completion_label: "SubscriptionError",
13388            completion_text: "SubscriptionError",
13389            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13390            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13391            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13392            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13393        },
13394        Run {
13395            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13396            initial_state: "Suˇscrirr".into(),
13397            buffer_marked_text: "<Su|scrirr>".into(),
13398            completion_label: "SubscriptionError",
13399            completion_text: "SubscriptionError",
13400            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13401            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13402            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13403            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13404        },
13405        Run {
13406            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13407            initial_state: "foo(indˇix)".into(),
13408            buffer_marked_text: "foo(<ind|ix>)".into(),
13409            completion_label: "node_index",
13410            completion_text: "node_index",
13411            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13412            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13413            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13414            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13415        },
13416        Run {
13417            run_description: "Replace range ends before cursor - should extend to cursor",
13418            initial_state: "before editˇo after".into(),
13419            buffer_marked_text: "before <{ed}>it|o after".into(),
13420            completion_label: "editor",
13421            completion_text: "editor",
13422            expected_with_insert_mode: "before editorˇo after".into(),
13423            expected_with_replace_mode: "before editorˇo after".into(),
13424            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13425            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13426        },
13427        Run {
13428            run_description: "Uses label for suffix matching",
13429            initial_state: "before ediˇtor after".into(),
13430            buffer_marked_text: "before <edi|tor> after".into(),
13431            completion_label: "editor",
13432            completion_text: "editor()",
13433            expected_with_insert_mode: "before editor()ˇtor after".into(),
13434            expected_with_replace_mode: "before editor()ˇ after".into(),
13435            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13436            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13437        },
13438        Run {
13439            run_description: "Case insensitive subsequence and suffix matching",
13440            initial_state: "before EDiˇtoR after".into(),
13441            buffer_marked_text: "before <EDi|toR> after".into(),
13442            completion_label: "editor",
13443            completion_text: "editor",
13444            expected_with_insert_mode: "before editorˇtoR after".into(),
13445            expected_with_replace_mode: "before editorˇ after".into(),
13446            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13447            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13448        },
13449    ];
13450
13451    for run in runs {
13452        let run_variations = [
13453            (LspInsertMode::Insert, run.expected_with_insert_mode),
13454            (LspInsertMode::Replace, run.expected_with_replace_mode),
13455            (
13456                LspInsertMode::ReplaceSubsequence,
13457                run.expected_with_replace_subsequence_mode,
13458            ),
13459            (
13460                LspInsertMode::ReplaceSuffix,
13461                run.expected_with_replace_suffix_mode,
13462            ),
13463        ];
13464
13465        for (lsp_insert_mode, expected_text) in run_variations {
13466            eprintln!(
13467                "run = {:?}, mode = {lsp_insert_mode:.?}",
13468                run.run_description,
13469            );
13470
13471            update_test_language_settings(&mut cx, |settings| {
13472                settings.defaults.completions = Some(CompletionSettingsContent {
13473                    lsp_insert_mode: Some(lsp_insert_mode),
13474                    words: Some(WordsCompletionMode::Disabled),
13475                    words_min_length: Some(0),
13476                    ..Default::default()
13477                });
13478            });
13479
13480            cx.set_state(&run.initial_state);
13481            cx.update_editor(|editor, window, cx| {
13482                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13483            });
13484
13485            let counter = Arc::new(AtomicUsize::new(0));
13486            handle_completion_request_with_insert_and_replace(
13487                &mut cx,
13488                &run.buffer_marked_text,
13489                vec![(run.completion_label, run.completion_text)],
13490                counter.clone(),
13491            )
13492            .await;
13493            cx.condition(|editor, _| editor.context_menu_visible())
13494                .await;
13495            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13496
13497            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13498                editor
13499                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13500                    .unwrap()
13501            });
13502            cx.assert_editor_state(&expected_text);
13503            handle_resolve_completion_request(&mut cx, None).await;
13504            apply_additional_edits.await.unwrap();
13505        }
13506    }
13507}
13508
13509#[gpui::test]
13510async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13511    init_test(cx, |_| {});
13512    let mut cx = EditorLspTestContext::new_rust(
13513        lsp::ServerCapabilities {
13514            completion_provider: Some(lsp::CompletionOptions {
13515                resolve_provider: Some(true),
13516                ..Default::default()
13517            }),
13518            ..Default::default()
13519        },
13520        cx,
13521    )
13522    .await;
13523
13524    let initial_state = "SubˇError";
13525    let buffer_marked_text = "<Sub|Error>";
13526    let completion_text = "SubscriptionError";
13527    let expected_with_insert_mode = "SubscriptionErrorˇError";
13528    let expected_with_replace_mode = "SubscriptionErrorˇ";
13529
13530    update_test_language_settings(&mut cx, |settings| {
13531        settings.defaults.completions = Some(CompletionSettingsContent {
13532            words: Some(WordsCompletionMode::Disabled),
13533            words_min_length: Some(0),
13534            // set the opposite here to ensure that the action is overriding the default behavior
13535            lsp_insert_mode: Some(LspInsertMode::Insert),
13536            ..Default::default()
13537        });
13538    });
13539
13540    cx.set_state(initial_state);
13541    cx.update_editor(|editor, window, cx| {
13542        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13543    });
13544
13545    let counter = Arc::new(AtomicUsize::new(0));
13546    handle_completion_request_with_insert_and_replace(
13547        &mut cx,
13548        buffer_marked_text,
13549        vec![(completion_text, completion_text)],
13550        counter.clone(),
13551    )
13552    .await;
13553    cx.condition(|editor, _| editor.context_menu_visible())
13554        .await;
13555    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13556
13557    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13558        editor
13559            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13560            .unwrap()
13561    });
13562    cx.assert_editor_state(expected_with_replace_mode);
13563    handle_resolve_completion_request(&mut cx, None).await;
13564    apply_additional_edits.await.unwrap();
13565
13566    update_test_language_settings(&mut cx, |settings| {
13567        settings.defaults.completions = Some(CompletionSettingsContent {
13568            words: Some(WordsCompletionMode::Disabled),
13569            words_min_length: Some(0),
13570            // set the opposite here to ensure that the action is overriding the default behavior
13571            lsp_insert_mode: Some(LspInsertMode::Replace),
13572            ..Default::default()
13573        });
13574    });
13575
13576    cx.set_state(initial_state);
13577    cx.update_editor(|editor, window, cx| {
13578        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13579    });
13580    handle_completion_request_with_insert_and_replace(
13581        &mut cx,
13582        buffer_marked_text,
13583        vec![(completion_text, completion_text)],
13584        counter.clone(),
13585    )
13586    .await;
13587    cx.condition(|editor, _| editor.context_menu_visible())
13588        .await;
13589    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13590
13591    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13592        editor
13593            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13594            .unwrap()
13595    });
13596    cx.assert_editor_state(expected_with_insert_mode);
13597    handle_resolve_completion_request(&mut cx, None).await;
13598    apply_additional_edits.await.unwrap();
13599}
13600
13601#[gpui::test]
13602async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
13603    init_test(cx, |_| {});
13604    let mut cx = EditorLspTestContext::new_rust(
13605        lsp::ServerCapabilities {
13606            completion_provider: Some(lsp::CompletionOptions {
13607                resolve_provider: Some(true),
13608                ..Default::default()
13609            }),
13610            ..Default::default()
13611        },
13612        cx,
13613    )
13614    .await;
13615
13616    // scenario: surrounding text matches completion text
13617    let completion_text = "to_offset";
13618    let initial_state = indoc! {"
13619        1. buf.to_offˇsuffix
13620        2. buf.to_offˇsuf
13621        3. buf.to_offˇfix
13622        4. buf.to_offˇ
13623        5. into_offˇensive
13624        6. ˇsuffix
13625        7. let ˇ //
13626        8. aaˇzz
13627        9. buf.to_off«zzzzzˇ»suffix
13628        10. buf.«ˇzzzzz»suffix
13629        11. to_off«ˇzzzzz»
13630
13631        buf.to_offˇsuffix  // newest cursor
13632    "};
13633    let completion_marked_buffer = indoc! {"
13634        1. buf.to_offsuffix
13635        2. buf.to_offsuf
13636        3. buf.to_offfix
13637        4. buf.to_off
13638        5. into_offensive
13639        6. suffix
13640        7. let  //
13641        8. aazz
13642        9. buf.to_offzzzzzsuffix
13643        10. buf.zzzzzsuffix
13644        11. to_offzzzzz
13645
13646        buf.<to_off|suffix>  // newest cursor
13647    "};
13648    let expected = indoc! {"
13649        1. buf.to_offsetˇ
13650        2. buf.to_offsetˇsuf
13651        3. buf.to_offsetˇfix
13652        4. buf.to_offsetˇ
13653        5. into_offsetˇensive
13654        6. to_offsetˇsuffix
13655        7. let to_offsetˇ //
13656        8. aato_offsetˇzz
13657        9. buf.to_offsetˇ
13658        10. buf.to_offsetˇsuffix
13659        11. to_offsetˇ
13660
13661        buf.to_offsetˇ  // newest cursor
13662    "};
13663    cx.set_state(initial_state);
13664    cx.update_editor(|editor, window, cx| {
13665        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13666    });
13667    handle_completion_request_with_insert_and_replace(
13668        &mut cx,
13669        completion_marked_buffer,
13670        vec![(completion_text, completion_text)],
13671        Arc::new(AtomicUsize::new(0)),
13672    )
13673    .await;
13674    cx.condition(|editor, _| editor.context_menu_visible())
13675        .await;
13676    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13677        editor
13678            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13679            .unwrap()
13680    });
13681    cx.assert_editor_state(expected);
13682    handle_resolve_completion_request(&mut cx, None).await;
13683    apply_additional_edits.await.unwrap();
13684
13685    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
13686    let completion_text = "foo_and_bar";
13687    let initial_state = indoc! {"
13688        1. ooanbˇ
13689        2. zooanbˇ
13690        3. ooanbˇz
13691        4. zooanbˇz
13692        5. ooanˇ
13693        6. oanbˇ
13694
13695        ooanbˇ
13696    "};
13697    let completion_marked_buffer = indoc! {"
13698        1. ooanb
13699        2. zooanb
13700        3. ooanbz
13701        4. zooanbz
13702        5. ooan
13703        6. oanb
13704
13705        <ooanb|>
13706    "};
13707    let expected = indoc! {"
13708        1. foo_and_barˇ
13709        2. zfoo_and_barˇ
13710        3. foo_and_barˇz
13711        4. zfoo_and_barˇz
13712        5. ooanfoo_and_barˇ
13713        6. oanbfoo_and_barˇ
13714
13715        foo_and_barˇ
13716    "};
13717    cx.set_state(initial_state);
13718    cx.update_editor(|editor, window, cx| {
13719        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13720    });
13721    handle_completion_request_with_insert_and_replace(
13722        &mut cx,
13723        completion_marked_buffer,
13724        vec![(completion_text, completion_text)],
13725        Arc::new(AtomicUsize::new(0)),
13726    )
13727    .await;
13728    cx.condition(|editor, _| editor.context_menu_visible())
13729        .await;
13730    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13731        editor
13732            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13733            .unwrap()
13734    });
13735    cx.assert_editor_state(expected);
13736    handle_resolve_completion_request(&mut cx, None).await;
13737    apply_additional_edits.await.unwrap();
13738
13739    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
13740    // (expects the same as if it was inserted at the end)
13741    let completion_text = "foo_and_bar";
13742    let initial_state = indoc! {"
13743        1. ooˇanb
13744        2. zooˇanb
13745        3. ooˇanbz
13746        4. zooˇanbz
13747
13748        ooˇanb
13749    "};
13750    let completion_marked_buffer = indoc! {"
13751        1. ooanb
13752        2. zooanb
13753        3. ooanbz
13754        4. zooanbz
13755
13756        <oo|anb>
13757    "};
13758    let expected = indoc! {"
13759        1. foo_and_barˇ
13760        2. zfoo_and_barˇ
13761        3. foo_and_barˇz
13762        4. zfoo_and_barˇz
13763
13764        foo_and_barˇ
13765    "};
13766    cx.set_state(initial_state);
13767    cx.update_editor(|editor, window, cx| {
13768        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13769    });
13770    handle_completion_request_with_insert_and_replace(
13771        &mut cx,
13772        completion_marked_buffer,
13773        vec![(completion_text, completion_text)],
13774        Arc::new(AtomicUsize::new(0)),
13775    )
13776    .await;
13777    cx.condition(|editor, _| editor.context_menu_visible())
13778        .await;
13779    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13780        editor
13781            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13782            .unwrap()
13783    });
13784    cx.assert_editor_state(expected);
13785    handle_resolve_completion_request(&mut cx, None).await;
13786    apply_additional_edits.await.unwrap();
13787}
13788
13789// This used to crash
13790#[gpui::test]
13791async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
13792    init_test(cx, |_| {});
13793
13794    let buffer_text = indoc! {"
13795        fn main() {
13796            10.satu;
13797
13798            //
13799            // separate cursors so they open in different excerpts (manually reproducible)
13800            //
13801
13802            10.satu20;
13803        }
13804    "};
13805    let multibuffer_text_with_selections = indoc! {"
13806        fn main() {
13807            10.satuˇ;
13808
13809            //
13810
13811            //
13812
13813            10.satuˇ20;
13814        }
13815    "};
13816    let expected_multibuffer = indoc! {"
13817        fn main() {
13818            10.saturating_sub()ˇ;
13819
13820            //
13821
13822            //
13823
13824            10.saturating_sub()ˇ;
13825        }
13826    "};
13827
13828    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
13829    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
13830
13831    let fs = FakeFs::new(cx.executor());
13832    fs.insert_tree(
13833        path!("/a"),
13834        json!({
13835            "main.rs": buffer_text,
13836        }),
13837    )
13838    .await;
13839
13840    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13841    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13842    language_registry.add(rust_lang());
13843    let mut fake_servers = language_registry.register_fake_lsp(
13844        "Rust",
13845        FakeLspAdapter {
13846            capabilities: lsp::ServerCapabilities {
13847                completion_provider: Some(lsp::CompletionOptions {
13848                    resolve_provider: None,
13849                    ..lsp::CompletionOptions::default()
13850                }),
13851                ..lsp::ServerCapabilities::default()
13852            },
13853            ..FakeLspAdapter::default()
13854        },
13855    );
13856    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13857    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13858    let buffer = project
13859        .update(cx, |project, cx| {
13860            project.open_local_buffer(path!("/a/main.rs"), cx)
13861        })
13862        .await
13863        .unwrap();
13864
13865    let multi_buffer = cx.new(|cx| {
13866        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
13867        multi_buffer.push_excerpts(
13868            buffer.clone(),
13869            [ExcerptRange::new(0..first_excerpt_end)],
13870            cx,
13871        );
13872        multi_buffer.push_excerpts(
13873            buffer.clone(),
13874            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
13875            cx,
13876        );
13877        multi_buffer
13878    });
13879
13880    let editor = workspace
13881        .update(cx, |_, window, cx| {
13882            cx.new(|cx| {
13883                Editor::new(
13884                    EditorMode::Full {
13885                        scale_ui_elements_with_buffer_font_size: false,
13886                        show_active_line_background: false,
13887                        sized_by_content: false,
13888                    },
13889                    multi_buffer.clone(),
13890                    Some(project.clone()),
13891                    window,
13892                    cx,
13893                )
13894            })
13895        })
13896        .unwrap();
13897
13898    let pane = workspace
13899        .update(cx, |workspace, _, _| workspace.active_pane().clone())
13900        .unwrap();
13901    pane.update_in(cx, |pane, window, cx| {
13902        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
13903    });
13904
13905    let fake_server = fake_servers.next().await.unwrap();
13906
13907    editor.update_in(cx, |editor, window, cx| {
13908        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13909            s.select_ranges([
13910                Point::new(1, 11)..Point::new(1, 11),
13911                Point::new(7, 11)..Point::new(7, 11),
13912            ])
13913        });
13914
13915        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
13916    });
13917
13918    editor.update_in(cx, |editor, window, cx| {
13919        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13920    });
13921
13922    fake_server
13923        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13924            let completion_item = lsp::CompletionItem {
13925                label: "saturating_sub()".into(),
13926                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
13927                    lsp::InsertReplaceEdit {
13928                        new_text: "saturating_sub()".to_owned(),
13929                        insert: lsp::Range::new(
13930                            lsp::Position::new(7, 7),
13931                            lsp::Position::new(7, 11),
13932                        ),
13933                        replace: lsp::Range::new(
13934                            lsp::Position::new(7, 7),
13935                            lsp::Position::new(7, 13),
13936                        ),
13937                    },
13938                )),
13939                ..lsp::CompletionItem::default()
13940            };
13941
13942            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
13943        })
13944        .next()
13945        .await
13946        .unwrap();
13947
13948    cx.condition(&editor, |editor, _| editor.context_menu_visible())
13949        .await;
13950
13951    editor
13952        .update_in(cx, |editor, window, cx| {
13953            editor
13954                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13955                .unwrap()
13956        })
13957        .await
13958        .unwrap();
13959
13960    editor.update(cx, |editor, cx| {
13961        assert_text_with_selections(editor, expected_multibuffer, cx);
13962    })
13963}
13964
13965#[gpui::test]
13966async fn test_completion(cx: &mut TestAppContext) {
13967    init_test(cx, |_| {});
13968
13969    let mut cx = EditorLspTestContext::new_rust(
13970        lsp::ServerCapabilities {
13971            completion_provider: Some(lsp::CompletionOptions {
13972                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13973                resolve_provider: Some(true),
13974                ..Default::default()
13975            }),
13976            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13977            ..Default::default()
13978        },
13979        cx,
13980    )
13981    .await;
13982    let counter = Arc::new(AtomicUsize::new(0));
13983
13984    cx.set_state(indoc! {"
13985        oneˇ
13986        two
13987        three
13988    "});
13989    cx.simulate_keystroke(".");
13990    handle_completion_request(
13991        indoc! {"
13992            one.|<>
13993            two
13994            three
13995        "},
13996        vec!["first_completion", "second_completion"],
13997        true,
13998        counter.clone(),
13999        &mut cx,
14000    )
14001    .await;
14002    cx.condition(|editor, _| editor.context_menu_visible())
14003        .await;
14004    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14005
14006    let _handler = handle_signature_help_request(
14007        &mut cx,
14008        lsp::SignatureHelp {
14009            signatures: vec![lsp::SignatureInformation {
14010                label: "test signature".to_string(),
14011                documentation: None,
14012                parameters: Some(vec![lsp::ParameterInformation {
14013                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
14014                    documentation: None,
14015                }]),
14016                active_parameter: None,
14017            }],
14018            active_signature: None,
14019            active_parameter: None,
14020        },
14021    );
14022    cx.update_editor(|editor, window, cx| {
14023        assert!(
14024            !editor.signature_help_state.is_shown(),
14025            "No signature help was called for"
14026        );
14027        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14028    });
14029    cx.run_until_parked();
14030    cx.update_editor(|editor, _, _| {
14031        assert!(
14032            !editor.signature_help_state.is_shown(),
14033            "No signature help should be shown when completions menu is open"
14034        );
14035    });
14036
14037    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14038        editor.context_menu_next(&Default::default(), window, cx);
14039        editor
14040            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14041            .unwrap()
14042    });
14043    cx.assert_editor_state(indoc! {"
14044        one.second_completionˇ
14045        two
14046        three
14047    "});
14048
14049    handle_resolve_completion_request(
14050        &mut cx,
14051        Some(vec![
14052            (
14053                //This overlaps with the primary completion edit which is
14054                //misbehavior from the LSP spec, test that we filter it out
14055                indoc! {"
14056                    one.second_ˇcompletion
14057                    two
14058                    threeˇ
14059                "},
14060                "overlapping additional edit",
14061            ),
14062            (
14063                indoc! {"
14064                    one.second_completion
14065                    two
14066                    threeˇ
14067                "},
14068                "\nadditional edit",
14069            ),
14070        ]),
14071    )
14072    .await;
14073    apply_additional_edits.await.unwrap();
14074    cx.assert_editor_state(indoc! {"
14075        one.second_completionˇ
14076        two
14077        three
14078        additional edit
14079    "});
14080
14081    cx.set_state(indoc! {"
14082        one.second_completion
14083        twoˇ
14084        threeˇ
14085        additional edit
14086    "});
14087    cx.simulate_keystroke(" ");
14088    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14089    cx.simulate_keystroke("s");
14090    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14091
14092    cx.assert_editor_state(indoc! {"
14093        one.second_completion
14094        two sˇ
14095        three sˇ
14096        additional edit
14097    "});
14098    handle_completion_request(
14099        indoc! {"
14100            one.second_completion
14101            two s
14102            three <s|>
14103            additional edit
14104        "},
14105        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14106        true,
14107        counter.clone(),
14108        &mut cx,
14109    )
14110    .await;
14111    cx.condition(|editor, _| editor.context_menu_visible())
14112        .await;
14113    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14114
14115    cx.simulate_keystroke("i");
14116
14117    handle_completion_request(
14118        indoc! {"
14119            one.second_completion
14120            two si
14121            three <si|>
14122            additional edit
14123        "},
14124        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14125        true,
14126        counter.clone(),
14127        &mut cx,
14128    )
14129    .await;
14130    cx.condition(|editor, _| editor.context_menu_visible())
14131        .await;
14132    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14133
14134    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14135        editor
14136            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14137            .unwrap()
14138    });
14139    cx.assert_editor_state(indoc! {"
14140        one.second_completion
14141        two sixth_completionˇ
14142        three sixth_completionˇ
14143        additional edit
14144    "});
14145
14146    apply_additional_edits.await.unwrap();
14147
14148    update_test_language_settings(&mut cx, |settings| {
14149        settings.defaults.show_completions_on_input = Some(false);
14150    });
14151    cx.set_state("editorˇ");
14152    cx.simulate_keystroke(".");
14153    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14154    cx.simulate_keystrokes("c l o");
14155    cx.assert_editor_state("editor.cloˇ");
14156    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14157    cx.update_editor(|editor, window, cx| {
14158        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14159    });
14160    handle_completion_request(
14161        "editor.<clo|>",
14162        vec!["close", "clobber"],
14163        true,
14164        counter.clone(),
14165        &mut cx,
14166    )
14167    .await;
14168    cx.condition(|editor, _| editor.context_menu_visible())
14169        .await;
14170    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14171
14172    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14173        editor
14174            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14175            .unwrap()
14176    });
14177    cx.assert_editor_state("editor.clobberˇ");
14178    handle_resolve_completion_request(&mut cx, None).await;
14179    apply_additional_edits.await.unwrap();
14180}
14181
14182#[gpui::test]
14183async fn test_completion_reuse(cx: &mut TestAppContext) {
14184    init_test(cx, |_| {});
14185
14186    let mut cx = EditorLspTestContext::new_rust(
14187        lsp::ServerCapabilities {
14188            completion_provider: Some(lsp::CompletionOptions {
14189                trigger_characters: Some(vec![".".to_string()]),
14190                ..Default::default()
14191            }),
14192            ..Default::default()
14193        },
14194        cx,
14195    )
14196    .await;
14197
14198    let counter = Arc::new(AtomicUsize::new(0));
14199    cx.set_state("objˇ");
14200    cx.simulate_keystroke(".");
14201
14202    // Initial completion request returns complete results
14203    let is_incomplete = false;
14204    handle_completion_request(
14205        "obj.|<>",
14206        vec!["a", "ab", "abc"],
14207        is_incomplete,
14208        counter.clone(),
14209        &mut cx,
14210    )
14211    .await;
14212    cx.run_until_parked();
14213    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14214    cx.assert_editor_state("obj.ˇ");
14215    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14216
14217    // Type "a" - filters existing completions
14218    cx.simulate_keystroke("a");
14219    cx.run_until_parked();
14220    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14221    cx.assert_editor_state("obj.aˇ");
14222    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14223
14224    // Type "b" - filters existing completions
14225    cx.simulate_keystroke("b");
14226    cx.run_until_parked();
14227    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14228    cx.assert_editor_state("obj.abˇ");
14229    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14230
14231    // Type "c" - filters existing completions
14232    cx.simulate_keystroke("c");
14233    cx.run_until_parked();
14234    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14235    cx.assert_editor_state("obj.abcˇ");
14236    check_displayed_completions(vec!["abc"], &mut cx);
14237
14238    // Backspace to delete "c" - filters existing completions
14239    cx.update_editor(|editor, window, cx| {
14240        editor.backspace(&Backspace, window, cx);
14241    });
14242    cx.run_until_parked();
14243    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14244    cx.assert_editor_state("obj.abˇ");
14245    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14246
14247    // Moving cursor to the left dismisses menu.
14248    cx.update_editor(|editor, window, cx| {
14249        editor.move_left(&MoveLeft, window, cx);
14250    });
14251    cx.run_until_parked();
14252    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14253    cx.assert_editor_state("obj.aˇb");
14254    cx.update_editor(|editor, _, _| {
14255        assert_eq!(editor.context_menu_visible(), false);
14256    });
14257
14258    // Type "b" - new request
14259    cx.simulate_keystroke("b");
14260    let is_incomplete = false;
14261    handle_completion_request(
14262        "obj.<ab|>a",
14263        vec!["ab", "abc"],
14264        is_incomplete,
14265        counter.clone(),
14266        &mut cx,
14267    )
14268    .await;
14269    cx.run_until_parked();
14270    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14271    cx.assert_editor_state("obj.abˇb");
14272    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14273
14274    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14275    cx.update_editor(|editor, window, cx| {
14276        editor.backspace(&Backspace, window, cx);
14277    });
14278    let is_incomplete = false;
14279    handle_completion_request(
14280        "obj.<a|>b",
14281        vec!["a", "ab", "abc"],
14282        is_incomplete,
14283        counter.clone(),
14284        &mut cx,
14285    )
14286    .await;
14287    cx.run_until_parked();
14288    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14289    cx.assert_editor_state("obj.aˇb");
14290    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14291
14292    // Backspace to delete "a" - dismisses menu.
14293    cx.update_editor(|editor, window, cx| {
14294        editor.backspace(&Backspace, window, cx);
14295    });
14296    cx.run_until_parked();
14297    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14298    cx.assert_editor_state("obj.ˇb");
14299    cx.update_editor(|editor, _, _| {
14300        assert_eq!(editor.context_menu_visible(), false);
14301    });
14302}
14303
14304#[gpui::test]
14305async fn test_word_completion(cx: &mut TestAppContext) {
14306    let lsp_fetch_timeout_ms = 10;
14307    init_test(cx, |language_settings| {
14308        language_settings.defaults.completions = Some(CompletionSettingsContent {
14309            words_min_length: Some(0),
14310            lsp_fetch_timeout_ms: Some(10),
14311            lsp_insert_mode: Some(LspInsertMode::Insert),
14312            ..Default::default()
14313        });
14314    });
14315
14316    let mut cx = EditorLspTestContext::new_rust(
14317        lsp::ServerCapabilities {
14318            completion_provider: Some(lsp::CompletionOptions {
14319                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14320                ..lsp::CompletionOptions::default()
14321            }),
14322            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14323            ..lsp::ServerCapabilities::default()
14324        },
14325        cx,
14326    )
14327    .await;
14328
14329    let throttle_completions = Arc::new(AtomicBool::new(false));
14330
14331    let lsp_throttle_completions = throttle_completions.clone();
14332    let _completion_requests_handler =
14333        cx.lsp
14334            .server
14335            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14336                let lsp_throttle_completions = lsp_throttle_completions.clone();
14337                let cx = cx.clone();
14338                async move {
14339                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14340                        cx.background_executor()
14341                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14342                            .await;
14343                    }
14344                    Ok(Some(lsp::CompletionResponse::Array(vec![
14345                        lsp::CompletionItem {
14346                            label: "first".into(),
14347                            ..lsp::CompletionItem::default()
14348                        },
14349                        lsp::CompletionItem {
14350                            label: "last".into(),
14351                            ..lsp::CompletionItem::default()
14352                        },
14353                    ])))
14354                }
14355            });
14356
14357    cx.set_state(indoc! {"
14358        oneˇ
14359        two
14360        three
14361    "});
14362    cx.simulate_keystroke(".");
14363    cx.executor().run_until_parked();
14364    cx.condition(|editor, _| editor.context_menu_visible())
14365        .await;
14366    cx.update_editor(|editor, window, cx| {
14367        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14368        {
14369            assert_eq!(
14370                completion_menu_entries(menu),
14371                &["first", "last"],
14372                "When LSP server is fast to reply, no fallback word completions are used"
14373            );
14374        } else {
14375            panic!("expected completion menu to be open");
14376        }
14377        editor.cancel(&Cancel, window, cx);
14378    });
14379    cx.executor().run_until_parked();
14380    cx.condition(|editor, _| !editor.context_menu_visible())
14381        .await;
14382
14383    throttle_completions.store(true, atomic::Ordering::Release);
14384    cx.simulate_keystroke(".");
14385    cx.executor()
14386        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14387    cx.executor().run_until_parked();
14388    cx.condition(|editor, _| editor.context_menu_visible())
14389        .await;
14390    cx.update_editor(|editor, _, _| {
14391        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14392        {
14393            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14394                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14395        } else {
14396            panic!("expected completion menu to be open");
14397        }
14398    });
14399}
14400
14401#[gpui::test]
14402async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14403    init_test(cx, |language_settings| {
14404        language_settings.defaults.completions = Some(CompletionSettingsContent {
14405            words: Some(WordsCompletionMode::Enabled),
14406            words_min_length: Some(0),
14407            lsp_insert_mode: Some(LspInsertMode::Insert),
14408            ..Default::default()
14409        });
14410    });
14411
14412    let mut cx = EditorLspTestContext::new_rust(
14413        lsp::ServerCapabilities {
14414            completion_provider: Some(lsp::CompletionOptions {
14415                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14416                ..lsp::CompletionOptions::default()
14417            }),
14418            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14419            ..lsp::ServerCapabilities::default()
14420        },
14421        cx,
14422    )
14423    .await;
14424
14425    let _completion_requests_handler =
14426        cx.lsp
14427            .server
14428            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14429                Ok(Some(lsp::CompletionResponse::Array(vec![
14430                    lsp::CompletionItem {
14431                        label: "first".into(),
14432                        ..lsp::CompletionItem::default()
14433                    },
14434                    lsp::CompletionItem {
14435                        label: "last".into(),
14436                        ..lsp::CompletionItem::default()
14437                    },
14438                ])))
14439            });
14440
14441    cx.set_state(indoc! {"ˇ
14442        first
14443        last
14444        second
14445    "});
14446    cx.simulate_keystroke(".");
14447    cx.executor().run_until_parked();
14448    cx.condition(|editor, _| editor.context_menu_visible())
14449        .await;
14450    cx.update_editor(|editor, _, _| {
14451        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14452        {
14453            assert_eq!(
14454                completion_menu_entries(menu),
14455                &["first", "last", "second"],
14456                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14457            );
14458        } else {
14459            panic!("expected completion menu to be open");
14460        }
14461    });
14462}
14463
14464#[gpui::test]
14465async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14466    init_test(cx, |language_settings| {
14467        language_settings.defaults.completions = Some(CompletionSettingsContent {
14468            words: Some(WordsCompletionMode::Disabled),
14469            words_min_length: Some(0),
14470            lsp_insert_mode: Some(LspInsertMode::Insert),
14471            ..Default::default()
14472        });
14473    });
14474
14475    let mut cx = EditorLspTestContext::new_rust(
14476        lsp::ServerCapabilities {
14477            completion_provider: Some(lsp::CompletionOptions {
14478                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14479                ..lsp::CompletionOptions::default()
14480            }),
14481            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14482            ..lsp::ServerCapabilities::default()
14483        },
14484        cx,
14485    )
14486    .await;
14487
14488    let _completion_requests_handler =
14489        cx.lsp
14490            .server
14491            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14492                panic!("LSP completions should not be queried when dealing with word completions")
14493            });
14494
14495    cx.set_state(indoc! {"ˇ
14496        first
14497        last
14498        second
14499    "});
14500    cx.update_editor(|editor, window, cx| {
14501        editor.show_word_completions(&ShowWordCompletions, window, cx);
14502    });
14503    cx.executor().run_until_parked();
14504    cx.condition(|editor, _| editor.context_menu_visible())
14505        .await;
14506    cx.update_editor(|editor, _, _| {
14507        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14508        {
14509            assert_eq!(
14510                completion_menu_entries(menu),
14511                &["first", "last", "second"],
14512                "`ShowWordCompletions` action should show word completions"
14513            );
14514        } else {
14515            panic!("expected completion menu to be open");
14516        }
14517    });
14518
14519    cx.simulate_keystroke("l");
14520    cx.executor().run_until_parked();
14521    cx.condition(|editor, _| editor.context_menu_visible())
14522        .await;
14523    cx.update_editor(|editor, _, _| {
14524        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14525        {
14526            assert_eq!(
14527                completion_menu_entries(menu),
14528                &["last"],
14529                "After showing word completions, further editing should filter them and not query the LSP"
14530            );
14531        } else {
14532            panic!("expected completion menu to be open");
14533        }
14534    });
14535}
14536
14537#[gpui::test]
14538async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14539    init_test(cx, |language_settings| {
14540        language_settings.defaults.completions = Some(CompletionSettingsContent {
14541            words_min_length: Some(0),
14542            lsp: Some(false),
14543            lsp_insert_mode: Some(LspInsertMode::Insert),
14544            ..Default::default()
14545        });
14546    });
14547
14548    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14549
14550    cx.set_state(indoc! {"ˇ
14551        0_usize
14552        let
14553        33
14554        4.5f32
14555    "});
14556    cx.update_editor(|editor, window, cx| {
14557        editor.show_completions(&ShowCompletions::default(), window, cx);
14558    });
14559    cx.executor().run_until_parked();
14560    cx.condition(|editor, _| editor.context_menu_visible())
14561        .await;
14562    cx.update_editor(|editor, window, cx| {
14563        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14564        {
14565            assert_eq!(
14566                completion_menu_entries(menu),
14567                &["let"],
14568                "With no digits in the completion query, no digits should be in the word completions"
14569            );
14570        } else {
14571            panic!("expected completion menu to be open");
14572        }
14573        editor.cancel(&Cancel, window, cx);
14574    });
14575
14576    cx.set_state(indoc! {"14577        0_usize
14578        let
14579        3
14580        33.35f32
14581    "});
14582    cx.update_editor(|editor, window, cx| {
14583        editor.show_completions(&ShowCompletions::default(), window, cx);
14584    });
14585    cx.executor().run_until_parked();
14586    cx.condition(|editor, _| editor.context_menu_visible())
14587        .await;
14588    cx.update_editor(|editor, _, _| {
14589        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14590        {
14591            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14592                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14593        } else {
14594            panic!("expected completion menu to be open");
14595        }
14596    });
14597}
14598
14599#[gpui::test]
14600async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
14601    init_test(cx, |language_settings| {
14602        language_settings.defaults.completions = Some(CompletionSettingsContent {
14603            words: Some(WordsCompletionMode::Enabled),
14604            words_min_length: Some(3),
14605            lsp_insert_mode: Some(LspInsertMode::Insert),
14606            ..Default::default()
14607        });
14608    });
14609
14610    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14611    cx.set_state(indoc! {"ˇ
14612        wow
14613        wowen
14614        wowser
14615    "});
14616    cx.simulate_keystroke("w");
14617    cx.executor().run_until_parked();
14618    cx.update_editor(|editor, _, _| {
14619        if editor.context_menu.borrow_mut().is_some() {
14620            panic!(
14621                "expected completion menu to be hidden, as words completion threshold is not met"
14622            );
14623        }
14624    });
14625
14626    cx.update_editor(|editor, window, cx| {
14627        editor.show_word_completions(&ShowWordCompletions, window, cx);
14628    });
14629    cx.executor().run_until_parked();
14630    cx.update_editor(|editor, window, cx| {
14631        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14632        {
14633            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");
14634        } else {
14635            panic!("expected completion menu to be open after the word completions are called with an action");
14636        }
14637
14638        editor.cancel(&Cancel, window, cx);
14639    });
14640    cx.update_editor(|editor, _, _| {
14641        if editor.context_menu.borrow_mut().is_some() {
14642            panic!("expected completion menu to be hidden after canceling");
14643        }
14644    });
14645
14646    cx.simulate_keystroke("o");
14647    cx.executor().run_until_parked();
14648    cx.update_editor(|editor, _, _| {
14649        if editor.context_menu.borrow_mut().is_some() {
14650            panic!(
14651                "expected completion menu to be hidden, as words completion threshold is not met still"
14652            );
14653        }
14654    });
14655
14656    cx.simulate_keystroke("w");
14657    cx.executor().run_until_parked();
14658    cx.update_editor(|editor, _, _| {
14659        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14660        {
14661            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
14662        } else {
14663            panic!("expected completion menu to be open after the word completions threshold is met");
14664        }
14665    });
14666}
14667
14668#[gpui::test]
14669async fn test_word_completions_disabled(cx: &mut TestAppContext) {
14670    init_test(cx, |language_settings| {
14671        language_settings.defaults.completions = Some(CompletionSettingsContent {
14672            words: Some(WordsCompletionMode::Enabled),
14673            words_min_length: Some(0),
14674            lsp_insert_mode: Some(LspInsertMode::Insert),
14675            ..Default::default()
14676        });
14677    });
14678
14679    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14680    cx.update_editor(|editor, _, _| {
14681        editor.disable_word_completions();
14682    });
14683    cx.set_state(indoc! {"ˇ
14684        wow
14685        wowen
14686        wowser
14687    "});
14688    cx.simulate_keystroke("w");
14689    cx.executor().run_until_parked();
14690    cx.update_editor(|editor, _, _| {
14691        if editor.context_menu.borrow_mut().is_some() {
14692            panic!(
14693                "expected completion menu to be hidden, as words completion are disabled for this editor"
14694            );
14695        }
14696    });
14697
14698    cx.update_editor(|editor, window, cx| {
14699        editor.show_word_completions(&ShowWordCompletions, window, cx);
14700    });
14701    cx.executor().run_until_parked();
14702    cx.update_editor(|editor, _, _| {
14703        if editor.context_menu.borrow_mut().is_some() {
14704            panic!(
14705                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
14706            );
14707        }
14708    });
14709}
14710
14711fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
14712    let position = || lsp::Position {
14713        line: params.text_document_position.position.line,
14714        character: params.text_document_position.position.character,
14715    };
14716    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14717        range: lsp::Range {
14718            start: position(),
14719            end: position(),
14720        },
14721        new_text: text.to_string(),
14722    }))
14723}
14724
14725#[gpui::test]
14726async fn test_multiline_completion(cx: &mut TestAppContext) {
14727    init_test(cx, |_| {});
14728
14729    let fs = FakeFs::new(cx.executor());
14730    fs.insert_tree(
14731        path!("/a"),
14732        json!({
14733            "main.ts": "a",
14734        }),
14735    )
14736    .await;
14737
14738    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14739    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14740    let typescript_language = Arc::new(Language::new(
14741        LanguageConfig {
14742            name: "TypeScript".into(),
14743            matcher: LanguageMatcher {
14744                path_suffixes: vec!["ts".to_string()],
14745                ..LanguageMatcher::default()
14746            },
14747            line_comments: vec!["// ".into()],
14748            ..LanguageConfig::default()
14749        },
14750        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14751    ));
14752    language_registry.add(typescript_language.clone());
14753    let mut fake_servers = language_registry.register_fake_lsp(
14754        "TypeScript",
14755        FakeLspAdapter {
14756            capabilities: lsp::ServerCapabilities {
14757                completion_provider: Some(lsp::CompletionOptions {
14758                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14759                    ..lsp::CompletionOptions::default()
14760                }),
14761                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14762                ..lsp::ServerCapabilities::default()
14763            },
14764            // Emulate vtsls label generation
14765            label_for_completion: Some(Box::new(|item, _| {
14766                let text = if let Some(description) = item
14767                    .label_details
14768                    .as_ref()
14769                    .and_then(|label_details| label_details.description.as_ref())
14770                {
14771                    format!("{} {}", item.label, description)
14772                } else if let Some(detail) = &item.detail {
14773                    format!("{} {}", item.label, detail)
14774                } else {
14775                    item.label.clone()
14776                };
14777                let len = text.len();
14778                Some(language::CodeLabel {
14779                    text,
14780                    runs: Vec::new(),
14781                    filter_range: 0..len,
14782                })
14783            })),
14784            ..FakeLspAdapter::default()
14785        },
14786    );
14787    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14788    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14789    let worktree_id = workspace
14790        .update(cx, |workspace, _window, cx| {
14791            workspace.project().update(cx, |project, cx| {
14792                project.worktrees(cx).next().unwrap().read(cx).id()
14793            })
14794        })
14795        .unwrap();
14796    let _buffer = project
14797        .update(cx, |project, cx| {
14798            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
14799        })
14800        .await
14801        .unwrap();
14802    let editor = workspace
14803        .update(cx, |workspace, window, cx| {
14804            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
14805        })
14806        .unwrap()
14807        .await
14808        .unwrap()
14809        .downcast::<Editor>()
14810        .unwrap();
14811    let fake_server = fake_servers.next().await.unwrap();
14812
14813    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
14814    let multiline_label_2 = "a\nb\nc\n";
14815    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
14816    let multiline_description = "d\ne\nf\n";
14817    let multiline_detail_2 = "g\nh\ni\n";
14818
14819    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
14820        move |params, _| async move {
14821            Ok(Some(lsp::CompletionResponse::Array(vec![
14822                lsp::CompletionItem {
14823                    label: multiline_label.to_string(),
14824                    text_edit: gen_text_edit(&params, "new_text_1"),
14825                    ..lsp::CompletionItem::default()
14826                },
14827                lsp::CompletionItem {
14828                    label: "single line label 1".to_string(),
14829                    detail: Some(multiline_detail.to_string()),
14830                    text_edit: gen_text_edit(&params, "new_text_2"),
14831                    ..lsp::CompletionItem::default()
14832                },
14833                lsp::CompletionItem {
14834                    label: "single line label 2".to_string(),
14835                    label_details: Some(lsp::CompletionItemLabelDetails {
14836                        description: Some(multiline_description.to_string()),
14837                        detail: None,
14838                    }),
14839                    text_edit: gen_text_edit(&params, "new_text_2"),
14840                    ..lsp::CompletionItem::default()
14841                },
14842                lsp::CompletionItem {
14843                    label: multiline_label_2.to_string(),
14844                    detail: Some(multiline_detail_2.to_string()),
14845                    text_edit: gen_text_edit(&params, "new_text_3"),
14846                    ..lsp::CompletionItem::default()
14847                },
14848                lsp::CompletionItem {
14849                    label: "Label with many     spaces and \t but without newlines".to_string(),
14850                    detail: Some(
14851                        "Details with many     spaces and \t but without newlines".to_string(),
14852                    ),
14853                    text_edit: gen_text_edit(&params, "new_text_4"),
14854                    ..lsp::CompletionItem::default()
14855                },
14856            ])))
14857        },
14858    );
14859
14860    editor.update_in(cx, |editor, window, cx| {
14861        cx.focus_self(window);
14862        editor.move_to_end(&MoveToEnd, window, cx);
14863        editor.handle_input(".", window, cx);
14864    });
14865    cx.run_until_parked();
14866    completion_handle.next().await.unwrap();
14867
14868    editor.update(cx, |editor, _| {
14869        assert!(editor.context_menu_visible());
14870        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14871        {
14872            let completion_labels = menu
14873                .completions
14874                .borrow()
14875                .iter()
14876                .map(|c| c.label.text.clone())
14877                .collect::<Vec<_>>();
14878            assert_eq!(
14879                completion_labels,
14880                &[
14881                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
14882                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
14883                    "single line label 2 d e f ",
14884                    "a b c g h i ",
14885                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
14886                ],
14887                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
14888            );
14889
14890            for completion in menu
14891                .completions
14892                .borrow()
14893                .iter() {
14894                    assert_eq!(
14895                        completion.label.filter_range,
14896                        0..completion.label.text.len(),
14897                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
14898                    );
14899                }
14900        } else {
14901            panic!("expected completion menu to be open");
14902        }
14903    });
14904}
14905
14906#[gpui::test]
14907async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
14908    init_test(cx, |_| {});
14909    let mut cx = EditorLspTestContext::new_rust(
14910        lsp::ServerCapabilities {
14911            completion_provider: Some(lsp::CompletionOptions {
14912                trigger_characters: Some(vec![".".to_string()]),
14913                ..Default::default()
14914            }),
14915            ..Default::default()
14916        },
14917        cx,
14918    )
14919    .await;
14920    cx.lsp
14921        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14922            Ok(Some(lsp::CompletionResponse::Array(vec![
14923                lsp::CompletionItem {
14924                    label: "first".into(),
14925                    ..Default::default()
14926                },
14927                lsp::CompletionItem {
14928                    label: "last".into(),
14929                    ..Default::default()
14930                },
14931            ])))
14932        });
14933    cx.set_state("variableˇ");
14934    cx.simulate_keystroke(".");
14935    cx.executor().run_until_parked();
14936
14937    cx.update_editor(|editor, _, _| {
14938        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14939        {
14940            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
14941        } else {
14942            panic!("expected completion menu to be open");
14943        }
14944    });
14945
14946    cx.update_editor(|editor, window, cx| {
14947        editor.move_page_down(&MovePageDown::default(), window, cx);
14948        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14949        {
14950            assert!(
14951                menu.selected_item == 1,
14952                "expected PageDown to select the last item from the context menu"
14953            );
14954        } else {
14955            panic!("expected completion menu to stay open after PageDown");
14956        }
14957    });
14958
14959    cx.update_editor(|editor, window, cx| {
14960        editor.move_page_up(&MovePageUp::default(), window, cx);
14961        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14962        {
14963            assert!(
14964                menu.selected_item == 0,
14965                "expected PageUp to select the first item from the context menu"
14966            );
14967        } else {
14968            panic!("expected completion menu to stay open after PageUp");
14969        }
14970    });
14971}
14972
14973#[gpui::test]
14974async fn test_as_is_completions(cx: &mut TestAppContext) {
14975    init_test(cx, |_| {});
14976    let mut cx = EditorLspTestContext::new_rust(
14977        lsp::ServerCapabilities {
14978            completion_provider: Some(lsp::CompletionOptions {
14979                ..Default::default()
14980            }),
14981            ..Default::default()
14982        },
14983        cx,
14984    )
14985    .await;
14986    cx.lsp
14987        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14988            Ok(Some(lsp::CompletionResponse::Array(vec![
14989                lsp::CompletionItem {
14990                    label: "unsafe".into(),
14991                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14992                        range: lsp::Range {
14993                            start: lsp::Position {
14994                                line: 1,
14995                                character: 2,
14996                            },
14997                            end: lsp::Position {
14998                                line: 1,
14999                                character: 3,
15000                            },
15001                        },
15002                        new_text: "unsafe".to_string(),
15003                    })),
15004                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
15005                    ..Default::default()
15006                },
15007            ])))
15008        });
15009    cx.set_state("fn a() {}\n");
15010    cx.executor().run_until_parked();
15011    cx.update_editor(|editor, window, cx| {
15012        editor.show_completions(
15013            &ShowCompletions {
15014                trigger: Some("\n".into()),
15015            },
15016            window,
15017            cx,
15018        );
15019    });
15020    cx.executor().run_until_parked();
15021
15022    cx.update_editor(|editor, window, cx| {
15023        editor.confirm_completion(&Default::default(), window, cx)
15024    });
15025    cx.executor().run_until_parked();
15026    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
15027}
15028
15029#[gpui::test]
15030async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
15031    init_test(cx, |_| {});
15032    let language =
15033        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
15034    let mut cx = EditorLspTestContext::new(
15035        language,
15036        lsp::ServerCapabilities {
15037            completion_provider: Some(lsp::CompletionOptions {
15038                ..lsp::CompletionOptions::default()
15039            }),
15040            ..lsp::ServerCapabilities::default()
15041        },
15042        cx,
15043    )
15044    .await;
15045
15046    cx.set_state(
15047        "#ifndef BAR_H
15048#define BAR_H
15049
15050#include <stdbool.h>
15051
15052int fn_branch(bool do_branch1, bool do_branch2);
15053
15054#endif // BAR_H
15055ˇ",
15056    );
15057    cx.executor().run_until_parked();
15058    cx.update_editor(|editor, window, cx| {
15059        editor.handle_input("#", window, cx);
15060    });
15061    cx.executor().run_until_parked();
15062    cx.update_editor(|editor, window, cx| {
15063        editor.handle_input("i", window, cx);
15064    });
15065    cx.executor().run_until_parked();
15066    cx.update_editor(|editor, window, cx| {
15067        editor.handle_input("n", window, cx);
15068    });
15069    cx.executor().run_until_parked();
15070    cx.assert_editor_state(
15071        "#ifndef BAR_H
15072#define BAR_H
15073
15074#include <stdbool.h>
15075
15076int fn_branch(bool do_branch1, bool do_branch2);
15077
15078#endif // BAR_H
15079#inˇ",
15080    );
15081
15082    cx.lsp
15083        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15084            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15085                is_incomplete: false,
15086                item_defaults: None,
15087                items: vec![lsp::CompletionItem {
15088                    kind: Some(lsp::CompletionItemKind::SNIPPET),
15089                    label_details: Some(lsp::CompletionItemLabelDetails {
15090                        detail: Some("header".to_string()),
15091                        description: None,
15092                    }),
15093                    label: " include".to_string(),
15094                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15095                        range: lsp::Range {
15096                            start: lsp::Position {
15097                                line: 8,
15098                                character: 1,
15099                            },
15100                            end: lsp::Position {
15101                                line: 8,
15102                                character: 1,
15103                            },
15104                        },
15105                        new_text: "include \"$0\"".to_string(),
15106                    })),
15107                    sort_text: Some("40b67681include".to_string()),
15108                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15109                    filter_text: Some("include".to_string()),
15110                    insert_text: Some("include \"$0\"".to_string()),
15111                    ..lsp::CompletionItem::default()
15112                }],
15113            })))
15114        });
15115    cx.update_editor(|editor, window, cx| {
15116        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15117    });
15118    cx.executor().run_until_parked();
15119    cx.update_editor(|editor, window, cx| {
15120        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15121    });
15122    cx.executor().run_until_parked();
15123    cx.assert_editor_state(
15124        "#ifndef BAR_H
15125#define BAR_H
15126
15127#include <stdbool.h>
15128
15129int fn_branch(bool do_branch1, bool do_branch2);
15130
15131#endif // BAR_H
15132#include \"ˇ\"",
15133    );
15134
15135    cx.lsp
15136        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15137            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15138                is_incomplete: true,
15139                item_defaults: None,
15140                items: vec![lsp::CompletionItem {
15141                    kind: Some(lsp::CompletionItemKind::FILE),
15142                    label: "AGL/".to_string(),
15143                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15144                        range: lsp::Range {
15145                            start: lsp::Position {
15146                                line: 8,
15147                                character: 10,
15148                            },
15149                            end: lsp::Position {
15150                                line: 8,
15151                                character: 11,
15152                            },
15153                        },
15154                        new_text: "AGL/".to_string(),
15155                    })),
15156                    sort_text: Some("40b67681AGL/".to_string()),
15157                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15158                    filter_text: Some("AGL/".to_string()),
15159                    insert_text: Some("AGL/".to_string()),
15160                    ..lsp::CompletionItem::default()
15161                }],
15162            })))
15163        });
15164    cx.update_editor(|editor, window, cx| {
15165        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15166    });
15167    cx.executor().run_until_parked();
15168    cx.update_editor(|editor, window, cx| {
15169        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15170    });
15171    cx.executor().run_until_parked();
15172    cx.assert_editor_state(
15173        r##"#ifndef BAR_H
15174#define BAR_H
15175
15176#include <stdbool.h>
15177
15178int fn_branch(bool do_branch1, bool do_branch2);
15179
15180#endif // BAR_H
15181#include "AGL/ˇ"##,
15182    );
15183
15184    cx.update_editor(|editor, window, cx| {
15185        editor.handle_input("\"", window, cx);
15186    });
15187    cx.executor().run_until_parked();
15188    cx.assert_editor_state(
15189        r##"#ifndef BAR_H
15190#define BAR_H
15191
15192#include <stdbool.h>
15193
15194int fn_branch(bool do_branch1, bool do_branch2);
15195
15196#endif // BAR_H
15197#include "AGL/"ˇ"##,
15198    );
15199}
15200
15201#[gpui::test]
15202async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15203    init_test(cx, |_| {});
15204
15205    let mut cx = EditorLspTestContext::new_rust(
15206        lsp::ServerCapabilities {
15207            completion_provider: Some(lsp::CompletionOptions {
15208                trigger_characters: Some(vec![".".to_string()]),
15209                resolve_provider: Some(true),
15210                ..Default::default()
15211            }),
15212            ..Default::default()
15213        },
15214        cx,
15215    )
15216    .await;
15217
15218    cx.set_state("fn main() { let a = 2ˇ; }");
15219    cx.simulate_keystroke(".");
15220    let completion_item = lsp::CompletionItem {
15221        label: "Some".into(),
15222        kind: Some(lsp::CompletionItemKind::SNIPPET),
15223        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15224        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15225            kind: lsp::MarkupKind::Markdown,
15226            value: "```rust\nSome(2)\n```".to_string(),
15227        })),
15228        deprecated: Some(false),
15229        sort_text: Some("Some".to_string()),
15230        filter_text: Some("Some".to_string()),
15231        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15232        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15233            range: lsp::Range {
15234                start: lsp::Position {
15235                    line: 0,
15236                    character: 22,
15237                },
15238                end: lsp::Position {
15239                    line: 0,
15240                    character: 22,
15241                },
15242            },
15243            new_text: "Some(2)".to_string(),
15244        })),
15245        additional_text_edits: Some(vec![lsp::TextEdit {
15246            range: lsp::Range {
15247                start: lsp::Position {
15248                    line: 0,
15249                    character: 20,
15250                },
15251                end: lsp::Position {
15252                    line: 0,
15253                    character: 22,
15254                },
15255            },
15256            new_text: "".to_string(),
15257        }]),
15258        ..Default::default()
15259    };
15260
15261    let closure_completion_item = completion_item.clone();
15262    let counter = Arc::new(AtomicUsize::new(0));
15263    let counter_clone = counter.clone();
15264    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15265        let task_completion_item = closure_completion_item.clone();
15266        counter_clone.fetch_add(1, atomic::Ordering::Release);
15267        async move {
15268            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15269                is_incomplete: true,
15270                item_defaults: None,
15271                items: vec![task_completion_item],
15272            })))
15273        }
15274    });
15275
15276    cx.condition(|editor, _| editor.context_menu_visible())
15277        .await;
15278    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15279    assert!(request.next().await.is_some());
15280    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15281
15282    cx.simulate_keystrokes("S o m");
15283    cx.condition(|editor, _| editor.context_menu_visible())
15284        .await;
15285    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15286    assert!(request.next().await.is_some());
15287    assert!(request.next().await.is_some());
15288    assert!(request.next().await.is_some());
15289    request.close();
15290    assert!(request.next().await.is_none());
15291    assert_eq!(
15292        counter.load(atomic::Ordering::Acquire),
15293        4,
15294        "With the completions menu open, only one LSP request should happen per input"
15295    );
15296}
15297
15298#[gpui::test]
15299async fn test_toggle_comment(cx: &mut TestAppContext) {
15300    init_test(cx, |_| {});
15301    let mut cx = EditorTestContext::new(cx).await;
15302    let language = Arc::new(Language::new(
15303        LanguageConfig {
15304            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15305            ..Default::default()
15306        },
15307        Some(tree_sitter_rust::LANGUAGE.into()),
15308    ));
15309    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15310
15311    // If multiple selections intersect a line, the line is only toggled once.
15312    cx.set_state(indoc! {"
15313        fn a() {
15314            «//b();
15315            ˇ»// «c();
15316            //ˇ»  d();
15317        }
15318    "});
15319
15320    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15321
15322    cx.assert_editor_state(indoc! {"
15323        fn a() {
15324            «b();
15325            c();
15326            ˇ» d();
15327        }
15328    "});
15329
15330    // The comment prefix is inserted at the same column for every line in a
15331    // selection.
15332    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15333
15334    cx.assert_editor_state(indoc! {"
15335        fn a() {
15336            // «b();
15337            // c();
15338            ˇ»//  d();
15339        }
15340    "});
15341
15342    // If a selection ends at the beginning of a line, that line is not toggled.
15343    cx.set_selections_state(indoc! {"
15344        fn a() {
15345            // b();
15346            «// c();
15347        ˇ»    //  d();
15348        }
15349    "});
15350
15351    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15352
15353    cx.assert_editor_state(indoc! {"
15354        fn a() {
15355            // b();
15356            «c();
15357        ˇ»    //  d();
15358        }
15359    "});
15360
15361    // If a selection span a single line and is empty, the line is toggled.
15362    cx.set_state(indoc! {"
15363        fn a() {
15364            a();
15365            b();
15366        ˇ
15367        }
15368    "});
15369
15370    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15371
15372    cx.assert_editor_state(indoc! {"
15373        fn a() {
15374            a();
15375            b();
15376        //•ˇ
15377        }
15378    "});
15379
15380    // If a selection span multiple lines, empty lines are not toggled.
15381    cx.set_state(indoc! {"
15382        fn a() {
15383            «a();
15384
15385            c();ˇ»
15386        }
15387    "});
15388
15389    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15390
15391    cx.assert_editor_state(indoc! {"
15392        fn a() {
15393            // «a();
15394
15395            // c();ˇ»
15396        }
15397    "});
15398
15399    // If a selection includes multiple comment prefixes, all lines are uncommented.
15400    cx.set_state(indoc! {"
15401        fn a() {
15402            «// a();
15403            /// b();
15404            //! c();ˇ»
15405        }
15406    "});
15407
15408    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15409
15410    cx.assert_editor_state(indoc! {"
15411        fn a() {
15412            «a();
15413            b();
15414            c();ˇ»
15415        }
15416    "});
15417}
15418
15419#[gpui::test]
15420async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15421    init_test(cx, |_| {});
15422    let mut cx = EditorTestContext::new(cx).await;
15423    let language = Arc::new(Language::new(
15424        LanguageConfig {
15425            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15426            ..Default::default()
15427        },
15428        Some(tree_sitter_rust::LANGUAGE.into()),
15429    ));
15430    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15431
15432    let toggle_comments = &ToggleComments {
15433        advance_downwards: false,
15434        ignore_indent: true,
15435    };
15436
15437    // If multiple selections intersect a line, the line is only toggled once.
15438    cx.set_state(indoc! {"
15439        fn a() {
15440        //    «b();
15441        //    c();
15442        //    ˇ» d();
15443        }
15444    "});
15445
15446    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15447
15448    cx.assert_editor_state(indoc! {"
15449        fn a() {
15450            «b();
15451            c();
15452            ˇ» d();
15453        }
15454    "});
15455
15456    // The comment prefix is inserted at the beginning of each line
15457    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15458
15459    cx.assert_editor_state(indoc! {"
15460        fn a() {
15461        //    «b();
15462        //    c();
15463        //    ˇ» d();
15464        }
15465    "});
15466
15467    // If a selection ends at the beginning of a line, that line is not toggled.
15468    cx.set_selections_state(indoc! {"
15469        fn a() {
15470        //    b();
15471        //    «c();
15472        ˇ»//     d();
15473        }
15474    "});
15475
15476    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15477
15478    cx.assert_editor_state(indoc! {"
15479        fn a() {
15480        //    b();
15481            «c();
15482        ˇ»//     d();
15483        }
15484    "});
15485
15486    // If a selection span a single line and is empty, the line is toggled.
15487    cx.set_state(indoc! {"
15488        fn a() {
15489            a();
15490            b();
15491        ˇ
15492        }
15493    "});
15494
15495    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15496
15497    cx.assert_editor_state(indoc! {"
15498        fn a() {
15499            a();
15500            b();
15501        //ˇ
15502        }
15503    "});
15504
15505    // If a selection span multiple lines, empty lines are not toggled.
15506    cx.set_state(indoc! {"
15507        fn a() {
15508            «a();
15509
15510            c();ˇ»
15511        }
15512    "});
15513
15514    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15515
15516    cx.assert_editor_state(indoc! {"
15517        fn a() {
15518        //    «a();
15519
15520        //    c();ˇ»
15521        }
15522    "});
15523
15524    // If a selection includes multiple comment prefixes, all lines are uncommented.
15525    cx.set_state(indoc! {"
15526        fn a() {
15527        //    «a();
15528        ///    b();
15529        //!    c();ˇ»
15530        }
15531    "});
15532
15533    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15534
15535    cx.assert_editor_state(indoc! {"
15536        fn a() {
15537            «a();
15538            b();
15539            c();ˇ»
15540        }
15541    "});
15542}
15543
15544#[gpui::test]
15545async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15546    init_test(cx, |_| {});
15547
15548    let language = Arc::new(Language::new(
15549        LanguageConfig {
15550            line_comments: vec!["// ".into()],
15551            ..Default::default()
15552        },
15553        Some(tree_sitter_rust::LANGUAGE.into()),
15554    ));
15555
15556    let mut cx = EditorTestContext::new(cx).await;
15557
15558    cx.language_registry().add(language.clone());
15559    cx.update_buffer(|buffer, cx| {
15560        buffer.set_language(Some(language), cx);
15561    });
15562
15563    let toggle_comments = &ToggleComments {
15564        advance_downwards: true,
15565        ignore_indent: false,
15566    };
15567
15568    // Single cursor on one line -> advance
15569    // Cursor moves horizontally 3 characters as well on non-blank line
15570    cx.set_state(indoc!(
15571        "fn a() {
15572             ˇdog();
15573             cat();
15574        }"
15575    ));
15576    cx.update_editor(|editor, window, cx| {
15577        editor.toggle_comments(toggle_comments, window, cx);
15578    });
15579    cx.assert_editor_state(indoc!(
15580        "fn a() {
15581             // dog();
15582             catˇ();
15583        }"
15584    ));
15585
15586    // Single selection on one line -> don't advance
15587    cx.set_state(indoc!(
15588        "fn a() {
15589             «dog()ˇ»;
15590             cat();
15591        }"
15592    ));
15593    cx.update_editor(|editor, window, cx| {
15594        editor.toggle_comments(toggle_comments, window, cx);
15595    });
15596    cx.assert_editor_state(indoc!(
15597        "fn a() {
15598             // «dog()ˇ»;
15599             cat();
15600        }"
15601    ));
15602
15603    // Multiple cursors on one line -> advance
15604    cx.set_state(indoc!(
15605        "fn a() {
15606             ˇdˇog();
15607             cat();
15608        }"
15609    ));
15610    cx.update_editor(|editor, window, cx| {
15611        editor.toggle_comments(toggle_comments, window, cx);
15612    });
15613    cx.assert_editor_state(indoc!(
15614        "fn a() {
15615             // dog();
15616             catˇ(ˇ);
15617        }"
15618    ));
15619
15620    // Multiple cursors on one line, with selection -> don't advance
15621    cx.set_state(indoc!(
15622        "fn a() {
15623             ˇdˇog«()ˇ»;
15624             cat();
15625        }"
15626    ));
15627    cx.update_editor(|editor, window, cx| {
15628        editor.toggle_comments(toggle_comments, window, cx);
15629    });
15630    cx.assert_editor_state(indoc!(
15631        "fn a() {
15632             // ˇdˇog«()ˇ»;
15633             cat();
15634        }"
15635    ));
15636
15637    // Single cursor on one line -> advance
15638    // Cursor moves to column 0 on blank line
15639    cx.set_state(indoc!(
15640        "fn a() {
15641             ˇdog();
15642
15643             cat();
15644        }"
15645    ));
15646    cx.update_editor(|editor, window, cx| {
15647        editor.toggle_comments(toggle_comments, window, cx);
15648    });
15649    cx.assert_editor_state(indoc!(
15650        "fn a() {
15651             // dog();
15652        ˇ
15653             cat();
15654        }"
15655    ));
15656
15657    // Single cursor on one line -> advance
15658    // Cursor starts and ends at column 0
15659    cx.set_state(indoc!(
15660        "fn a() {
15661         ˇ    dog();
15662             cat();
15663        }"
15664    ));
15665    cx.update_editor(|editor, window, cx| {
15666        editor.toggle_comments(toggle_comments, window, cx);
15667    });
15668    cx.assert_editor_state(indoc!(
15669        "fn a() {
15670             // dog();
15671         ˇ    cat();
15672        }"
15673    ));
15674}
15675
15676#[gpui::test]
15677async fn test_toggle_block_comment(cx: &mut TestAppContext) {
15678    init_test(cx, |_| {});
15679
15680    let mut cx = EditorTestContext::new(cx).await;
15681
15682    let html_language = Arc::new(
15683        Language::new(
15684            LanguageConfig {
15685                name: "HTML".into(),
15686                block_comment: Some(BlockCommentConfig {
15687                    start: "<!-- ".into(),
15688                    prefix: "".into(),
15689                    end: " -->".into(),
15690                    tab_size: 0,
15691                }),
15692                ..Default::default()
15693            },
15694            Some(tree_sitter_html::LANGUAGE.into()),
15695        )
15696        .with_injection_query(
15697            r#"
15698            (script_element
15699                (raw_text) @injection.content
15700                (#set! injection.language "javascript"))
15701            "#,
15702        )
15703        .unwrap(),
15704    );
15705
15706    let javascript_language = Arc::new(Language::new(
15707        LanguageConfig {
15708            name: "JavaScript".into(),
15709            line_comments: vec!["// ".into()],
15710            ..Default::default()
15711        },
15712        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15713    ));
15714
15715    cx.language_registry().add(html_language.clone());
15716    cx.language_registry().add(javascript_language);
15717    cx.update_buffer(|buffer, cx| {
15718        buffer.set_language(Some(html_language), cx);
15719    });
15720
15721    // Toggle comments for empty selections
15722    cx.set_state(
15723        &r#"
15724            <p>A</p>ˇ
15725            <p>B</p>ˇ
15726            <p>C</p>ˇ
15727        "#
15728        .unindent(),
15729    );
15730    cx.update_editor(|editor, window, cx| {
15731        editor.toggle_comments(&ToggleComments::default(), window, cx)
15732    });
15733    cx.assert_editor_state(
15734        &r#"
15735            <!-- <p>A</p>ˇ -->
15736            <!-- <p>B</p>ˇ -->
15737            <!-- <p>C</p>ˇ -->
15738        "#
15739        .unindent(),
15740    );
15741    cx.update_editor(|editor, window, cx| {
15742        editor.toggle_comments(&ToggleComments::default(), window, cx)
15743    });
15744    cx.assert_editor_state(
15745        &r#"
15746            <p>A</p>ˇ
15747            <p>B</p>ˇ
15748            <p>C</p>ˇ
15749        "#
15750        .unindent(),
15751    );
15752
15753    // Toggle comments for mixture of empty and non-empty selections, where
15754    // multiple selections occupy a given line.
15755    cx.set_state(
15756        &r#"
15757            <p>A«</p>
15758            <p>ˇ»B</p>ˇ
15759            <p>C«</p>
15760            <p>ˇ»D</p>ˇ
15761        "#
15762        .unindent(),
15763    );
15764
15765    cx.update_editor(|editor, window, cx| {
15766        editor.toggle_comments(&ToggleComments::default(), window, cx)
15767    });
15768    cx.assert_editor_state(
15769        &r#"
15770            <!-- <p>A«</p>
15771            <p>ˇ»B</p>ˇ -->
15772            <!-- <p>C«</p>
15773            <p>ˇ»D</p>ˇ -->
15774        "#
15775        .unindent(),
15776    );
15777    cx.update_editor(|editor, window, cx| {
15778        editor.toggle_comments(&ToggleComments::default(), window, cx)
15779    });
15780    cx.assert_editor_state(
15781        &r#"
15782            <p>A«</p>
15783            <p>ˇ»B</p>ˇ
15784            <p>C«</p>
15785            <p>ˇ»D</p>ˇ
15786        "#
15787        .unindent(),
15788    );
15789
15790    // Toggle comments when different languages are active for different
15791    // selections.
15792    cx.set_state(
15793        &r#"
15794            ˇ<script>
15795                ˇvar x = new Y();
15796            ˇ</script>
15797        "#
15798        .unindent(),
15799    );
15800    cx.executor().run_until_parked();
15801    cx.update_editor(|editor, window, cx| {
15802        editor.toggle_comments(&ToggleComments::default(), window, cx)
15803    });
15804    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
15805    // Uncommenting and commenting from this position brings in even more wrong artifacts.
15806    cx.assert_editor_state(
15807        &r#"
15808            <!-- ˇ<script> -->
15809                // ˇvar x = new Y();
15810            <!-- ˇ</script> -->
15811        "#
15812        .unindent(),
15813    );
15814}
15815
15816#[gpui::test]
15817fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
15818    init_test(cx, |_| {});
15819
15820    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15821    let multibuffer = cx.new(|cx| {
15822        let mut multibuffer = MultiBuffer::new(ReadWrite);
15823        multibuffer.push_excerpts(
15824            buffer.clone(),
15825            [
15826                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
15827                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
15828            ],
15829            cx,
15830        );
15831        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
15832        multibuffer
15833    });
15834
15835    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15836    editor.update_in(cx, |editor, window, cx| {
15837        assert_eq!(editor.text(cx), "aaaa\nbbbb");
15838        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15839            s.select_ranges([
15840                Point::new(0, 0)..Point::new(0, 0),
15841                Point::new(1, 0)..Point::new(1, 0),
15842            ])
15843        });
15844
15845        editor.handle_input("X", window, cx);
15846        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
15847        assert_eq!(
15848            editor.selections.ranges(cx),
15849            [
15850                Point::new(0, 1)..Point::new(0, 1),
15851                Point::new(1, 1)..Point::new(1, 1),
15852            ]
15853        );
15854
15855        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
15856        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15857            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
15858        });
15859        editor.backspace(&Default::default(), window, cx);
15860        assert_eq!(editor.text(cx), "Xa\nbbb");
15861        assert_eq!(
15862            editor.selections.ranges(cx),
15863            [Point::new(1, 0)..Point::new(1, 0)]
15864        );
15865
15866        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15867            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
15868        });
15869        editor.backspace(&Default::default(), window, cx);
15870        assert_eq!(editor.text(cx), "X\nbb");
15871        assert_eq!(
15872            editor.selections.ranges(cx),
15873            [Point::new(0, 1)..Point::new(0, 1)]
15874        );
15875    });
15876}
15877
15878#[gpui::test]
15879fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
15880    init_test(cx, |_| {});
15881
15882    let markers = vec![('[', ']').into(), ('(', ')').into()];
15883    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
15884        indoc! {"
15885            [aaaa
15886            (bbbb]
15887            cccc)",
15888        },
15889        markers.clone(),
15890    );
15891    let excerpt_ranges = markers.into_iter().map(|marker| {
15892        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
15893        ExcerptRange::new(context)
15894    });
15895    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
15896    let multibuffer = cx.new(|cx| {
15897        let mut multibuffer = MultiBuffer::new(ReadWrite);
15898        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
15899        multibuffer
15900    });
15901
15902    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15903    editor.update_in(cx, |editor, window, cx| {
15904        let (expected_text, selection_ranges) = marked_text_ranges(
15905            indoc! {"
15906                aaaa
15907                bˇbbb
15908                bˇbbˇb
15909                cccc"
15910            },
15911            true,
15912        );
15913        assert_eq!(editor.text(cx), expected_text);
15914        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15915            s.select_ranges(selection_ranges)
15916        });
15917
15918        editor.handle_input("X", window, cx);
15919
15920        let (expected_text, expected_selections) = marked_text_ranges(
15921            indoc! {"
15922                aaaa
15923                bXˇbbXb
15924                bXˇbbXˇb
15925                cccc"
15926            },
15927            false,
15928        );
15929        assert_eq!(editor.text(cx), expected_text);
15930        assert_eq!(editor.selections.ranges(cx), expected_selections);
15931
15932        editor.newline(&Newline, window, cx);
15933        let (expected_text, expected_selections) = marked_text_ranges(
15934            indoc! {"
15935                aaaa
15936                bX
15937                ˇbbX
15938                b
15939                bX
15940                ˇbbX
15941                ˇb
15942                cccc"
15943            },
15944            false,
15945        );
15946        assert_eq!(editor.text(cx), expected_text);
15947        assert_eq!(editor.selections.ranges(cx), expected_selections);
15948    });
15949}
15950
15951#[gpui::test]
15952fn test_refresh_selections(cx: &mut TestAppContext) {
15953    init_test(cx, |_| {});
15954
15955    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15956    let mut excerpt1_id = None;
15957    let multibuffer = cx.new(|cx| {
15958        let mut multibuffer = MultiBuffer::new(ReadWrite);
15959        excerpt1_id = multibuffer
15960            .push_excerpts(
15961                buffer.clone(),
15962                [
15963                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15964                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15965                ],
15966                cx,
15967            )
15968            .into_iter()
15969            .next();
15970        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15971        multibuffer
15972    });
15973
15974    let editor = cx.add_window(|window, cx| {
15975        let mut editor = build_editor(multibuffer.clone(), window, cx);
15976        let snapshot = editor.snapshot(window, cx);
15977        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15978            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
15979        });
15980        editor.begin_selection(
15981            Point::new(2, 1).to_display_point(&snapshot),
15982            true,
15983            1,
15984            window,
15985            cx,
15986        );
15987        assert_eq!(
15988            editor.selections.ranges(cx),
15989            [
15990                Point::new(1, 3)..Point::new(1, 3),
15991                Point::new(2, 1)..Point::new(2, 1),
15992            ]
15993        );
15994        editor
15995    });
15996
15997    // Refreshing selections is a no-op when excerpts haven't changed.
15998    _ = editor.update(cx, |editor, window, cx| {
15999        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16000        assert_eq!(
16001            editor.selections.ranges(cx),
16002            [
16003                Point::new(1, 3)..Point::new(1, 3),
16004                Point::new(2, 1)..Point::new(2, 1),
16005            ]
16006        );
16007    });
16008
16009    multibuffer.update(cx, |multibuffer, cx| {
16010        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16011    });
16012    _ = editor.update(cx, |editor, window, cx| {
16013        // Removing an excerpt causes the first selection to become degenerate.
16014        assert_eq!(
16015            editor.selections.ranges(cx),
16016            [
16017                Point::new(0, 0)..Point::new(0, 0),
16018                Point::new(0, 1)..Point::new(0, 1)
16019            ]
16020        );
16021
16022        // Refreshing selections will relocate the first selection to the original buffer
16023        // location.
16024        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16025        assert_eq!(
16026            editor.selections.ranges(cx),
16027            [
16028                Point::new(0, 1)..Point::new(0, 1),
16029                Point::new(0, 3)..Point::new(0, 3)
16030            ]
16031        );
16032        assert!(editor.selections.pending_anchor().is_some());
16033    });
16034}
16035
16036#[gpui::test]
16037fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
16038    init_test(cx, |_| {});
16039
16040    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16041    let mut excerpt1_id = None;
16042    let multibuffer = cx.new(|cx| {
16043        let mut multibuffer = MultiBuffer::new(ReadWrite);
16044        excerpt1_id = multibuffer
16045            .push_excerpts(
16046                buffer.clone(),
16047                [
16048                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16049                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16050                ],
16051                cx,
16052            )
16053            .into_iter()
16054            .next();
16055        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16056        multibuffer
16057    });
16058
16059    let editor = cx.add_window(|window, cx| {
16060        let mut editor = build_editor(multibuffer.clone(), window, cx);
16061        let snapshot = editor.snapshot(window, cx);
16062        editor.begin_selection(
16063            Point::new(1, 3).to_display_point(&snapshot),
16064            false,
16065            1,
16066            window,
16067            cx,
16068        );
16069        assert_eq!(
16070            editor.selections.ranges(cx),
16071            [Point::new(1, 3)..Point::new(1, 3)]
16072        );
16073        editor
16074    });
16075
16076    multibuffer.update(cx, |multibuffer, cx| {
16077        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16078    });
16079    _ = editor.update(cx, |editor, window, cx| {
16080        assert_eq!(
16081            editor.selections.ranges(cx),
16082            [Point::new(0, 0)..Point::new(0, 0)]
16083        );
16084
16085        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
16086        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16087        assert_eq!(
16088            editor.selections.ranges(cx),
16089            [Point::new(0, 3)..Point::new(0, 3)]
16090        );
16091        assert!(editor.selections.pending_anchor().is_some());
16092    });
16093}
16094
16095#[gpui::test]
16096async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
16097    init_test(cx, |_| {});
16098
16099    let language = Arc::new(
16100        Language::new(
16101            LanguageConfig {
16102                brackets: BracketPairConfig {
16103                    pairs: vec![
16104                        BracketPair {
16105                            start: "{".to_string(),
16106                            end: "}".to_string(),
16107                            close: true,
16108                            surround: true,
16109                            newline: true,
16110                        },
16111                        BracketPair {
16112                            start: "/* ".to_string(),
16113                            end: " */".to_string(),
16114                            close: true,
16115                            surround: true,
16116                            newline: true,
16117                        },
16118                    ],
16119                    ..Default::default()
16120                },
16121                ..Default::default()
16122            },
16123            Some(tree_sitter_rust::LANGUAGE.into()),
16124        )
16125        .with_indents_query("")
16126        .unwrap(),
16127    );
16128
16129    let text = concat!(
16130        "{   }\n",     //
16131        "  x\n",       //
16132        "  /*   */\n", //
16133        "x\n",         //
16134        "{{} }\n",     //
16135    );
16136
16137    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16138    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16139    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16140    editor
16141        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16142        .await;
16143
16144    editor.update_in(cx, |editor, window, cx| {
16145        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16146            s.select_display_ranges([
16147                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16148                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16149                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16150            ])
16151        });
16152        editor.newline(&Newline, window, cx);
16153
16154        assert_eq!(
16155            editor.buffer().read(cx).read(cx).text(),
16156            concat!(
16157                "{ \n",    // Suppress rustfmt
16158                "\n",      //
16159                "}\n",     //
16160                "  x\n",   //
16161                "  /* \n", //
16162                "  \n",    //
16163                "  */\n",  //
16164                "x\n",     //
16165                "{{} \n",  //
16166                "}\n",     //
16167            )
16168        );
16169    });
16170}
16171
16172#[gpui::test]
16173fn test_highlighted_ranges(cx: &mut TestAppContext) {
16174    init_test(cx, |_| {});
16175
16176    let editor = cx.add_window(|window, cx| {
16177        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16178        build_editor(buffer, window, cx)
16179    });
16180
16181    _ = editor.update(cx, |editor, window, cx| {
16182        struct Type1;
16183        struct Type2;
16184
16185        let buffer = editor.buffer.read(cx).snapshot(cx);
16186
16187        let anchor_range =
16188            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16189
16190        editor.highlight_background::<Type1>(
16191            &[
16192                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16193                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16194                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16195                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16196            ],
16197            |_| Hsla::red(),
16198            cx,
16199        );
16200        editor.highlight_background::<Type2>(
16201            &[
16202                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16203                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16204                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16205                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16206            ],
16207            |_| Hsla::green(),
16208            cx,
16209        );
16210
16211        let snapshot = editor.snapshot(window, cx);
16212        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16213            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16214            &snapshot,
16215            cx.theme(),
16216        );
16217        assert_eq!(
16218            highlighted_ranges,
16219            &[
16220                (
16221                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16222                    Hsla::green(),
16223                ),
16224                (
16225                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16226                    Hsla::red(),
16227                ),
16228                (
16229                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16230                    Hsla::green(),
16231                ),
16232                (
16233                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16234                    Hsla::red(),
16235                ),
16236            ]
16237        );
16238        assert_eq!(
16239            editor.sorted_background_highlights_in_range(
16240                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16241                &snapshot,
16242                cx.theme(),
16243            ),
16244            &[(
16245                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16246                Hsla::red(),
16247            )]
16248        );
16249    });
16250}
16251
16252#[gpui::test]
16253async fn test_following(cx: &mut TestAppContext) {
16254    init_test(cx, |_| {});
16255
16256    let fs = FakeFs::new(cx.executor());
16257    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16258
16259    let buffer = project.update(cx, |project, cx| {
16260        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16261        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16262    });
16263    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16264    let follower = cx.update(|cx| {
16265        cx.open_window(
16266            WindowOptions {
16267                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16268                    gpui::Point::new(px(0.), px(0.)),
16269                    gpui::Point::new(px(10.), px(80.)),
16270                ))),
16271                ..Default::default()
16272            },
16273            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16274        )
16275        .unwrap()
16276    });
16277
16278    let is_still_following = Rc::new(RefCell::new(true));
16279    let follower_edit_event_count = Rc::new(RefCell::new(0));
16280    let pending_update = Rc::new(RefCell::new(None));
16281    let leader_entity = leader.root(cx).unwrap();
16282    let follower_entity = follower.root(cx).unwrap();
16283    _ = follower.update(cx, {
16284        let update = pending_update.clone();
16285        let is_still_following = is_still_following.clone();
16286        let follower_edit_event_count = follower_edit_event_count.clone();
16287        |_, window, cx| {
16288            cx.subscribe_in(
16289                &leader_entity,
16290                window,
16291                move |_, leader, event, window, cx| {
16292                    leader.read(cx).add_event_to_update_proto(
16293                        event,
16294                        &mut update.borrow_mut(),
16295                        window,
16296                        cx,
16297                    );
16298                },
16299            )
16300            .detach();
16301
16302            cx.subscribe_in(
16303                &follower_entity,
16304                window,
16305                move |_, _, event: &EditorEvent, _window, _cx| {
16306                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16307                        *is_still_following.borrow_mut() = false;
16308                    }
16309
16310                    if let EditorEvent::BufferEdited = event {
16311                        *follower_edit_event_count.borrow_mut() += 1;
16312                    }
16313                },
16314            )
16315            .detach();
16316        }
16317    });
16318
16319    // Update the selections only
16320    _ = leader.update(cx, |leader, window, cx| {
16321        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16322            s.select_ranges([1..1])
16323        });
16324    });
16325    follower
16326        .update(cx, |follower, window, cx| {
16327            follower.apply_update_proto(
16328                &project,
16329                pending_update.borrow_mut().take().unwrap(),
16330                window,
16331                cx,
16332            )
16333        })
16334        .unwrap()
16335        .await
16336        .unwrap();
16337    _ = follower.update(cx, |follower, _, cx| {
16338        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
16339    });
16340    assert!(*is_still_following.borrow());
16341    assert_eq!(*follower_edit_event_count.borrow(), 0);
16342
16343    // Update the scroll position only
16344    _ = leader.update(cx, |leader, window, cx| {
16345        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16346    });
16347    follower
16348        .update(cx, |follower, window, cx| {
16349            follower.apply_update_proto(
16350                &project,
16351                pending_update.borrow_mut().take().unwrap(),
16352                window,
16353                cx,
16354            )
16355        })
16356        .unwrap()
16357        .await
16358        .unwrap();
16359    assert_eq!(
16360        follower
16361            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16362            .unwrap(),
16363        gpui::Point::new(1.5, 3.5)
16364    );
16365    assert!(*is_still_following.borrow());
16366    assert_eq!(*follower_edit_event_count.borrow(), 0);
16367
16368    // Update the selections and scroll position. The follower's scroll position is updated
16369    // via autoscroll, not via the leader's exact scroll position.
16370    _ = leader.update(cx, |leader, window, cx| {
16371        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16372            s.select_ranges([0..0])
16373        });
16374        leader.request_autoscroll(Autoscroll::newest(), cx);
16375        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16376    });
16377    follower
16378        .update(cx, |follower, window, cx| {
16379            follower.apply_update_proto(
16380                &project,
16381                pending_update.borrow_mut().take().unwrap(),
16382                window,
16383                cx,
16384            )
16385        })
16386        .unwrap()
16387        .await
16388        .unwrap();
16389    _ = follower.update(cx, |follower, _, cx| {
16390        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16391        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
16392    });
16393    assert!(*is_still_following.borrow());
16394
16395    // Creating a pending selection that precedes another selection
16396    _ = leader.update(cx, |leader, window, cx| {
16397        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16398            s.select_ranges([1..1])
16399        });
16400        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16401    });
16402    follower
16403        .update(cx, |follower, window, cx| {
16404            follower.apply_update_proto(
16405                &project,
16406                pending_update.borrow_mut().take().unwrap(),
16407                window,
16408                cx,
16409            )
16410        })
16411        .unwrap()
16412        .await
16413        .unwrap();
16414    _ = follower.update(cx, |follower, _, cx| {
16415        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
16416    });
16417    assert!(*is_still_following.borrow());
16418
16419    // Extend the pending selection so that it surrounds another selection
16420    _ = leader.update(cx, |leader, window, cx| {
16421        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16422    });
16423    follower
16424        .update(cx, |follower, window, cx| {
16425            follower.apply_update_proto(
16426                &project,
16427                pending_update.borrow_mut().take().unwrap(),
16428                window,
16429                cx,
16430            )
16431        })
16432        .unwrap()
16433        .await
16434        .unwrap();
16435    _ = follower.update(cx, |follower, _, cx| {
16436        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
16437    });
16438
16439    // Scrolling locally breaks the follow
16440    _ = follower.update(cx, |follower, window, cx| {
16441        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16442        follower.set_scroll_anchor(
16443            ScrollAnchor {
16444                anchor: top_anchor,
16445                offset: gpui::Point::new(0.0, 0.5),
16446            },
16447            window,
16448            cx,
16449        );
16450    });
16451    assert!(!(*is_still_following.borrow()));
16452}
16453
16454#[gpui::test]
16455async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16456    init_test(cx, |_| {});
16457
16458    let fs = FakeFs::new(cx.executor());
16459    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16460    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16461    let pane = workspace
16462        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16463        .unwrap();
16464
16465    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16466
16467    let leader = pane.update_in(cx, |_, window, cx| {
16468        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16469        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16470    });
16471
16472    // Start following the editor when it has no excerpts.
16473    let mut state_message =
16474        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16475    let workspace_entity = workspace.root(cx).unwrap();
16476    let follower_1 = cx
16477        .update_window(*workspace.deref(), |_, window, cx| {
16478            Editor::from_state_proto(
16479                workspace_entity,
16480                ViewId {
16481                    creator: CollaboratorId::PeerId(PeerId::default()),
16482                    id: 0,
16483                },
16484                &mut state_message,
16485                window,
16486                cx,
16487            )
16488        })
16489        .unwrap()
16490        .unwrap()
16491        .await
16492        .unwrap();
16493
16494    let update_message = Rc::new(RefCell::new(None));
16495    follower_1.update_in(cx, {
16496        let update = update_message.clone();
16497        |_, window, cx| {
16498            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16499                leader.read(cx).add_event_to_update_proto(
16500                    event,
16501                    &mut update.borrow_mut(),
16502                    window,
16503                    cx,
16504                );
16505            })
16506            .detach();
16507        }
16508    });
16509
16510    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16511        (
16512            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16513            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16514        )
16515    });
16516
16517    // Insert some excerpts.
16518    leader.update(cx, |leader, cx| {
16519        leader.buffer.update(cx, |multibuffer, cx| {
16520            multibuffer.set_excerpts_for_path(
16521                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
16522                buffer_1.clone(),
16523                vec![
16524                    Point::row_range(0..3),
16525                    Point::row_range(1..6),
16526                    Point::row_range(12..15),
16527                ],
16528                0,
16529                cx,
16530            );
16531            multibuffer.set_excerpts_for_path(
16532                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
16533                buffer_2.clone(),
16534                vec![Point::row_range(0..6), Point::row_range(8..12)],
16535                0,
16536                cx,
16537            );
16538        });
16539    });
16540
16541    // Apply the update of adding the excerpts.
16542    follower_1
16543        .update_in(cx, |follower, window, cx| {
16544            follower.apply_update_proto(
16545                &project,
16546                update_message.borrow().clone().unwrap(),
16547                window,
16548                cx,
16549            )
16550        })
16551        .await
16552        .unwrap();
16553    assert_eq!(
16554        follower_1.update(cx, |editor, cx| editor.text(cx)),
16555        leader.update(cx, |editor, cx| editor.text(cx))
16556    );
16557    update_message.borrow_mut().take();
16558
16559    // Start following separately after it already has excerpts.
16560    let mut state_message =
16561        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16562    let workspace_entity = workspace.root(cx).unwrap();
16563    let follower_2 = cx
16564        .update_window(*workspace.deref(), |_, window, cx| {
16565            Editor::from_state_proto(
16566                workspace_entity,
16567                ViewId {
16568                    creator: CollaboratorId::PeerId(PeerId::default()),
16569                    id: 0,
16570                },
16571                &mut state_message,
16572                window,
16573                cx,
16574            )
16575        })
16576        .unwrap()
16577        .unwrap()
16578        .await
16579        .unwrap();
16580    assert_eq!(
16581        follower_2.update(cx, |editor, cx| editor.text(cx)),
16582        leader.update(cx, |editor, cx| editor.text(cx))
16583    );
16584
16585    // Remove some excerpts.
16586    leader.update(cx, |leader, cx| {
16587        leader.buffer.update(cx, |multibuffer, cx| {
16588            let excerpt_ids = multibuffer.excerpt_ids();
16589            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16590            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16591        });
16592    });
16593
16594    // Apply the update of removing the excerpts.
16595    follower_1
16596        .update_in(cx, |follower, window, cx| {
16597            follower.apply_update_proto(
16598                &project,
16599                update_message.borrow().clone().unwrap(),
16600                window,
16601                cx,
16602            )
16603        })
16604        .await
16605        .unwrap();
16606    follower_2
16607        .update_in(cx, |follower, window, cx| {
16608            follower.apply_update_proto(
16609                &project,
16610                update_message.borrow().clone().unwrap(),
16611                window,
16612                cx,
16613            )
16614        })
16615        .await
16616        .unwrap();
16617    update_message.borrow_mut().take();
16618    assert_eq!(
16619        follower_1.update(cx, |editor, cx| editor.text(cx)),
16620        leader.update(cx, |editor, cx| editor.text(cx))
16621    );
16622}
16623
16624#[gpui::test]
16625async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16626    init_test(cx, |_| {});
16627
16628    let mut cx = EditorTestContext::new(cx).await;
16629    let lsp_store =
16630        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
16631
16632    cx.set_state(indoc! {"
16633        ˇfn func(abc def: i32) -> u32 {
16634        }
16635    "});
16636
16637    cx.update(|_, cx| {
16638        lsp_store.update(cx, |lsp_store, cx| {
16639            lsp_store
16640                .update_diagnostics(
16641                    LanguageServerId(0),
16642                    lsp::PublishDiagnosticsParams {
16643                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
16644                        version: None,
16645                        diagnostics: vec![
16646                            lsp::Diagnostic {
16647                                range: lsp::Range::new(
16648                                    lsp::Position::new(0, 11),
16649                                    lsp::Position::new(0, 12),
16650                                ),
16651                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16652                                ..Default::default()
16653                            },
16654                            lsp::Diagnostic {
16655                                range: lsp::Range::new(
16656                                    lsp::Position::new(0, 12),
16657                                    lsp::Position::new(0, 15),
16658                                ),
16659                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16660                                ..Default::default()
16661                            },
16662                            lsp::Diagnostic {
16663                                range: lsp::Range::new(
16664                                    lsp::Position::new(0, 25),
16665                                    lsp::Position::new(0, 28),
16666                                ),
16667                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16668                                ..Default::default()
16669                            },
16670                        ],
16671                    },
16672                    None,
16673                    DiagnosticSourceKind::Pushed,
16674                    &[],
16675                    cx,
16676                )
16677                .unwrap()
16678        });
16679    });
16680
16681    executor.run_until_parked();
16682
16683    cx.update_editor(|editor, window, cx| {
16684        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16685    });
16686
16687    cx.assert_editor_state(indoc! {"
16688        fn func(abc def: i32) -> ˇu32 {
16689        }
16690    "});
16691
16692    cx.update_editor(|editor, window, cx| {
16693        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16694    });
16695
16696    cx.assert_editor_state(indoc! {"
16697        fn func(abc ˇdef: i32) -> u32 {
16698        }
16699    "});
16700
16701    cx.update_editor(|editor, window, cx| {
16702        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16703    });
16704
16705    cx.assert_editor_state(indoc! {"
16706        fn func(abcˇ def: i32) -> u32 {
16707        }
16708    "});
16709
16710    cx.update_editor(|editor, window, cx| {
16711        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16712    });
16713
16714    cx.assert_editor_state(indoc! {"
16715        fn func(abc def: i32) -> ˇu32 {
16716        }
16717    "});
16718}
16719
16720#[gpui::test]
16721async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16722    init_test(cx, |_| {});
16723
16724    let mut cx = EditorTestContext::new(cx).await;
16725
16726    let diff_base = r#"
16727        use some::mod;
16728
16729        const A: u32 = 42;
16730
16731        fn main() {
16732            println!("hello");
16733
16734            println!("world");
16735        }
16736        "#
16737    .unindent();
16738
16739    // Edits are modified, removed, modified, added
16740    cx.set_state(
16741        &r#"
16742        use some::modified;
16743
16744        ˇ
16745        fn main() {
16746            println!("hello there");
16747
16748            println!("around the");
16749            println!("world");
16750        }
16751        "#
16752        .unindent(),
16753    );
16754
16755    cx.set_head_text(&diff_base);
16756    executor.run_until_parked();
16757
16758    cx.update_editor(|editor, window, cx| {
16759        //Wrap around the bottom of the buffer
16760        for _ in 0..3 {
16761            editor.go_to_next_hunk(&GoToHunk, window, cx);
16762        }
16763    });
16764
16765    cx.assert_editor_state(
16766        &r#"
16767        ˇuse some::modified;
16768
16769
16770        fn main() {
16771            println!("hello there");
16772
16773            println!("around the");
16774            println!("world");
16775        }
16776        "#
16777        .unindent(),
16778    );
16779
16780    cx.update_editor(|editor, window, cx| {
16781        //Wrap around the top of the buffer
16782        for _ in 0..2 {
16783            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16784        }
16785    });
16786
16787    cx.assert_editor_state(
16788        &r#"
16789        use some::modified;
16790
16791
16792        fn main() {
16793        ˇ    println!("hello there");
16794
16795            println!("around the");
16796            println!("world");
16797        }
16798        "#
16799        .unindent(),
16800    );
16801
16802    cx.update_editor(|editor, window, cx| {
16803        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16804    });
16805
16806    cx.assert_editor_state(
16807        &r#"
16808        use some::modified;
16809
16810        ˇ
16811        fn main() {
16812            println!("hello there");
16813
16814            println!("around the");
16815            println!("world");
16816        }
16817        "#
16818        .unindent(),
16819    );
16820
16821    cx.update_editor(|editor, window, cx| {
16822        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16823    });
16824
16825    cx.assert_editor_state(
16826        &r#"
16827        ˇuse some::modified;
16828
16829
16830        fn main() {
16831            println!("hello there");
16832
16833            println!("around the");
16834            println!("world");
16835        }
16836        "#
16837        .unindent(),
16838    );
16839
16840    cx.update_editor(|editor, window, cx| {
16841        for _ in 0..2 {
16842            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16843        }
16844    });
16845
16846    cx.assert_editor_state(
16847        &r#"
16848        use some::modified;
16849
16850
16851        fn main() {
16852        ˇ    println!("hello there");
16853
16854            println!("around the");
16855            println!("world");
16856        }
16857        "#
16858        .unindent(),
16859    );
16860
16861    cx.update_editor(|editor, window, cx| {
16862        editor.fold(&Fold, window, cx);
16863    });
16864
16865    cx.update_editor(|editor, window, cx| {
16866        editor.go_to_next_hunk(&GoToHunk, window, cx);
16867    });
16868
16869    cx.assert_editor_state(
16870        &r#"
16871        ˇuse some::modified;
16872
16873
16874        fn main() {
16875            println!("hello there");
16876
16877            println!("around the");
16878            println!("world");
16879        }
16880        "#
16881        .unindent(),
16882    );
16883}
16884
16885#[test]
16886fn test_split_words() {
16887    fn split(text: &str) -> Vec<&str> {
16888        split_words(text).collect()
16889    }
16890
16891    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
16892    assert_eq!(split("hello_world"), &["hello_", "world"]);
16893    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
16894    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
16895    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
16896    assert_eq!(split("helloworld"), &["helloworld"]);
16897
16898    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
16899}
16900
16901#[gpui::test]
16902async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
16903    init_test(cx, |_| {});
16904
16905    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
16906    let mut assert = |before, after| {
16907        let _state_context = cx.set_state(before);
16908        cx.run_until_parked();
16909        cx.update_editor(|editor, window, cx| {
16910            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
16911        });
16912        cx.run_until_parked();
16913        cx.assert_editor_state(after);
16914    };
16915
16916    // Outside bracket jumps to outside of matching bracket
16917    assert("console.logˇ(var);", "console.log(var)ˇ;");
16918    assert("console.log(var)ˇ;", "console.logˇ(var);");
16919
16920    // Inside bracket jumps to inside of matching bracket
16921    assert("console.log(ˇvar);", "console.log(varˇ);");
16922    assert("console.log(varˇ);", "console.log(ˇvar);");
16923
16924    // When outside a bracket and inside, favor jumping to the inside bracket
16925    assert(
16926        "console.log('foo', [1, 2, 3]ˇ);",
16927        "console.log(ˇ'foo', [1, 2, 3]);",
16928    );
16929    assert(
16930        "console.log(ˇ'foo', [1, 2, 3]);",
16931        "console.log('foo', [1, 2, 3]ˇ);",
16932    );
16933
16934    // Bias forward if two options are equally likely
16935    assert(
16936        "let result = curried_fun()ˇ();",
16937        "let result = curried_fun()()ˇ;",
16938    );
16939
16940    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
16941    assert(
16942        indoc! {"
16943            function test() {
16944                console.log('test')ˇ
16945            }"},
16946        indoc! {"
16947            function test() {
16948                console.logˇ('test')
16949            }"},
16950    );
16951}
16952
16953#[gpui::test]
16954async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
16955    init_test(cx, |_| {});
16956
16957    let fs = FakeFs::new(cx.executor());
16958    fs.insert_tree(
16959        path!("/a"),
16960        json!({
16961            "main.rs": "fn main() { let a = 5; }",
16962            "other.rs": "// Test file",
16963        }),
16964    )
16965    .await;
16966    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16967
16968    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16969    language_registry.add(Arc::new(Language::new(
16970        LanguageConfig {
16971            name: "Rust".into(),
16972            matcher: LanguageMatcher {
16973                path_suffixes: vec!["rs".to_string()],
16974                ..Default::default()
16975            },
16976            brackets: BracketPairConfig {
16977                pairs: vec![BracketPair {
16978                    start: "{".to_string(),
16979                    end: "}".to_string(),
16980                    close: true,
16981                    surround: true,
16982                    newline: true,
16983                }],
16984                disabled_scopes_by_bracket_ix: Vec::new(),
16985            },
16986            ..Default::default()
16987        },
16988        Some(tree_sitter_rust::LANGUAGE.into()),
16989    )));
16990    let mut fake_servers = language_registry.register_fake_lsp(
16991        "Rust",
16992        FakeLspAdapter {
16993            capabilities: lsp::ServerCapabilities {
16994                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
16995                    first_trigger_character: "{".to_string(),
16996                    more_trigger_character: None,
16997                }),
16998                ..Default::default()
16999            },
17000            ..Default::default()
17001        },
17002    );
17003
17004    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17005
17006    let cx = &mut VisualTestContext::from_window(*workspace, cx);
17007
17008    let worktree_id = workspace
17009        .update(cx, |workspace, _, cx| {
17010            workspace.project().update(cx, |project, cx| {
17011                project.worktrees(cx).next().unwrap().read(cx).id()
17012            })
17013        })
17014        .unwrap();
17015
17016    let buffer = project
17017        .update(cx, |project, cx| {
17018            project.open_local_buffer(path!("/a/main.rs"), cx)
17019        })
17020        .await
17021        .unwrap();
17022    let editor_handle = workspace
17023        .update(cx, |workspace, window, cx| {
17024            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
17025        })
17026        .unwrap()
17027        .await
17028        .unwrap()
17029        .downcast::<Editor>()
17030        .unwrap();
17031
17032    cx.executor().start_waiting();
17033    let fake_server = fake_servers.next().await.unwrap();
17034
17035    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
17036        |params, _| async move {
17037            assert_eq!(
17038                params.text_document_position.text_document.uri,
17039                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
17040            );
17041            assert_eq!(
17042                params.text_document_position.position,
17043                lsp::Position::new(0, 21),
17044            );
17045
17046            Ok(Some(vec![lsp::TextEdit {
17047                new_text: "]".to_string(),
17048                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17049            }]))
17050        },
17051    );
17052
17053    editor_handle.update_in(cx, |editor, window, cx| {
17054        window.focus(&editor.focus_handle(cx));
17055        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17056            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
17057        });
17058        editor.handle_input("{", window, cx);
17059    });
17060
17061    cx.executor().run_until_parked();
17062
17063    buffer.update(cx, |buffer, _| {
17064        assert_eq!(
17065            buffer.text(),
17066            "fn main() { let a = {5}; }",
17067            "No extra braces from on type formatting should appear in the buffer"
17068        )
17069    });
17070}
17071
17072#[gpui::test(iterations = 20, seeds(31))]
17073async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
17074    init_test(cx, |_| {});
17075
17076    let mut cx = EditorLspTestContext::new_rust(
17077        lsp::ServerCapabilities {
17078            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17079                first_trigger_character: ".".to_string(),
17080                more_trigger_character: None,
17081            }),
17082            ..Default::default()
17083        },
17084        cx,
17085    )
17086    .await;
17087
17088    cx.update_buffer(|buffer, _| {
17089        // This causes autoindent to be async.
17090        buffer.set_sync_parse_timeout(Duration::ZERO)
17091    });
17092
17093    cx.set_state("fn c() {\n    d()ˇ\n}\n");
17094    cx.simulate_keystroke("\n");
17095    cx.run_until_parked();
17096
17097    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
17098    let mut request =
17099        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
17100            let buffer_cloned = buffer_cloned.clone();
17101            async move {
17102                buffer_cloned.update(&mut cx, |buffer, _| {
17103                    assert_eq!(
17104                        buffer.text(),
17105                        "fn c() {\n    d()\n        .\n}\n",
17106                        "OnTypeFormatting should triggered after autoindent applied"
17107                    )
17108                })?;
17109
17110                Ok(Some(vec![]))
17111            }
17112        });
17113
17114    cx.simulate_keystroke(".");
17115    cx.run_until_parked();
17116
17117    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
17118    assert!(request.next().await.is_some());
17119    request.close();
17120    assert!(request.next().await.is_none());
17121}
17122
17123#[gpui::test]
17124async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17125    init_test(cx, |_| {});
17126
17127    let fs = FakeFs::new(cx.executor());
17128    fs.insert_tree(
17129        path!("/a"),
17130        json!({
17131            "main.rs": "fn main() { let a = 5; }",
17132            "other.rs": "// Test file",
17133        }),
17134    )
17135    .await;
17136
17137    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17138
17139    let server_restarts = Arc::new(AtomicUsize::new(0));
17140    let closure_restarts = Arc::clone(&server_restarts);
17141    let language_server_name = "test language server";
17142    let language_name: LanguageName = "Rust".into();
17143
17144    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17145    language_registry.add(Arc::new(Language::new(
17146        LanguageConfig {
17147            name: language_name.clone(),
17148            matcher: LanguageMatcher {
17149                path_suffixes: vec!["rs".to_string()],
17150                ..Default::default()
17151            },
17152            ..Default::default()
17153        },
17154        Some(tree_sitter_rust::LANGUAGE.into()),
17155    )));
17156    let mut fake_servers = language_registry.register_fake_lsp(
17157        "Rust",
17158        FakeLspAdapter {
17159            name: language_server_name,
17160            initialization_options: Some(json!({
17161                "testOptionValue": true
17162            })),
17163            initializer: Some(Box::new(move |fake_server| {
17164                let task_restarts = Arc::clone(&closure_restarts);
17165                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17166                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17167                    futures::future::ready(Ok(()))
17168                });
17169            })),
17170            ..Default::default()
17171        },
17172    );
17173
17174    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17175    let _buffer = project
17176        .update(cx, |project, cx| {
17177            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17178        })
17179        .await
17180        .unwrap();
17181    let _fake_server = fake_servers.next().await.unwrap();
17182    update_test_language_settings(cx, |language_settings| {
17183        language_settings.languages.0.insert(
17184            language_name.clone().0,
17185            LanguageSettingsContent {
17186                tab_size: NonZeroU32::new(8),
17187                ..Default::default()
17188            },
17189        );
17190    });
17191    cx.executor().run_until_parked();
17192    assert_eq!(
17193        server_restarts.load(atomic::Ordering::Acquire),
17194        0,
17195        "Should not restart LSP server on an unrelated change"
17196    );
17197
17198    update_test_project_settings(cx, |project_settings| {
17199        project_settings.lsp.insert(
17200            "Some other server name".into(),
17201            LspSettings {
17202                binary: None,
17203                settings: None,
17204                initialization_options: Some(json!({
17205                    "some other init value": false
17206                })),
17207                enable_lsp_tasks: false,
17208                fetch: None,
17209            },
17210        );
17211    });
17212    cx.executor().run_until_parked();
17213    assert_eq!(
17214        server_restarts.load(atomic::Ordering::Acquire),
17215        0,
17216        "Should not restart LSP server on an unrelated LSP settings change"
17217    );
17218
17219    update_test_project_settings(cx, |project_settings| {
17220        project_settings.lsp.insert(
17221            language_server_name.into(),
17222            LspSettings {
17223                binary: None,
17224                settings: None,
17225                initialization_options: Some(json!({
17226                    "anotherInitValue": false
17227                })),
17228                enable_lsp_tasks: false,
17229                fetch: None,
17230            },
17231        );
17232    });
17233    cx.executor().run_until_parked();
17234    assert_eq!(
17235        server_restarts.load(atomic::Ordering::Acquire),
17236        1,
17237        "Should restart LSP server on a related LSP settings change"
17238    );
17239
17240    update_test_project_settings(cx, |project_settings| {
17241        project_settings.lsp.insert(
17242            language_server_name.into(),
17243            LspSettings {
17244                binary: None,
17245                settings: None,
17246                initialization_options: Some(json!({
17247                    "anotherInitValue": false
17248                })),
17249                enable_lsp_tasks: false,
17250                fetch: None,
17251            },
17252        );
17253    });
17254    cx.executor().run_until_parked();
17255    assert_eq!(
17256        server_restarts.load(atomic::Ordering::Acquire),
17257        1,
17258        "Should not restart LSP server on a related LSP settings change that is the same"
17259    );
17260
17261    update_test_project_settings(cx, |project_settings| {
17262        project_settings.lsp.insert(
17263            language_server_name.into(),
17264            LspSettings {
17265                binary: None,
17266                settings: None,
17267                initialization_options: None,
17268                enable_lsp_tasks: false,
17269                fetch: None,
17270            },
17271        );
17272    });
17273    cx.executor().run_until_parked();
17274    assert_eq!(
17275        server_restarts.load(atomic::Ordering::Acquire),
17276        2,
17277        "Should restart LSP server on another related LSP settings change"
17278    );
17279}
17280
17281#[gpui::test]
17282async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17283    init_test(cx, |_| {});
17284
17285    let mut cx = EditorLspTestContext::new_rust(
17286        lsp::ServerCapabilities {
17287            completion_provider: Some(lsp::CompletionOptions {
17288                trigger_characters: Some(vec![".".to_string()]),
17289                resolve_provider: Some(true),
17290                ..Default::default()
17291            }),
17292            ..Default::default()
17293        },
17294        cx,
17295    )
17296    .await;
17297
17298    cx.set_state("fn main() { let a = 2ˇ; }");
17299    cx.simulate_keystroke(".");
17300    let completion_item = lsp::CompletionItem {
17301        label: "some".into(),
17302        kind: Some(lsp::CompletionItemKind::SNIPPET),
17303        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17304        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17305            kind: lsp::MarkupKind::Markdown,
17306            value: "```rust\nSome(2)\n```".to_string(),
17307        })),
17308        deprecated: Some(false),
17309        sort_text: Some("fffffff2".to_string()),
17310        filter_text: Some("some".to_string()),
17311        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17312        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17313            range: lsp::Range {
17314                start: lsp::Position {
17315                    line: 0,
17316                    character: 22,
17317                },
17318                end: lsp::Position {
17319                    line: 0,
17320                    character: 22,
17321                },
17322            },
17323            new_text: "Some(2)".to_string(),
17324        })),
17325        additional_text_edits: Some(vec![lsp::TextEdit {
17326            range: lsp::Range {
17327                start: lsp::Position {
17328                    line: 0,
17329                    character: 20,
17330                },
17331                end: lsp::Position {
17332                    line: 0,
17333                    character: 22,
17334                },
17335            },
17336            new_text: "".to_string(),
17337        }]),
17338        ..Default::default()
17339    };
17340
17341    let closure_completion_item = completion_item.clone();
17342    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17343        let task_completion_item = closure_completion_item.clone();
17344        async move {
17345            Ok(Some(lsp::CompletionResponse::Array(vec![
17346                task_completion_item,
17347            ])))
17348        }
17349    });
17350
17351    request.next().await;
17352
17353    cx.condition(|editor, _| editor.context_menu_visible())
17354        .await;
17355    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17356        editor
17357            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17358            .unwrap()
17359    });
17360    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17361
17362    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17363        let task_completion_item = completion_item.clone();
17364        async move { Ok(task_completion_item) }
17365    })
17366    .next()
17367    .await
17368    .unwrap();
17369    apply_additional_edits.await.unwrap();
17370    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17371}
17372
17373#[gpui::test]
17374async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17375    init_test(cx, |_| {});
17376
17377    let mut cx = EditorLspTestContext::new_rust(
17378        lsp::ServerCapabilities {
17379            completion_provider: Some(lsp::CompletionOptions {
17380                trigger_characters: Some(vec![".".to_string()]),
17381                resolve_provider: Some(true),
17382                ..Default::default()
17383            }),
17384            ..Default::default()
17385        },
17386        cx,
17387    )
17388    .await;
17389
17390    cx.set_state("fn main() { let a = 2ˇ; }");
17391    cx.simulate_keystroke(".");
17392
17393    let item1 = lsp::CompletionItem {
17394        label: "method id()".to_string(),
17395        filter_text: Some("id".to_string()),
17396        detail: None,
17397        documentation: None,
17398        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17399            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17400            new_text: ".id".to_string(),
17401        })),
17402        ..lsp::CompletionItem::default()
17403    };
17404
17405    let item2 = lsp::CompletionItem {
17406        label: "other".to_string(),
17407        filter_text: Some("other".to_string()),
17408        detail: None,
17409        documentation: None,
17410        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17411            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17412            new_text: ".other".to_string(),
17413        })),
17414        ..lsp::CompletionItem::default()
17415    };
17416
17417    let item1 = item1.clone();
17418    cx.set_request_handler::<lsp::request::Completion, _, _>({
17419        let item1 = item1.clone();
17420        move |_, _, _| {
17421            let item1 = item1.clone();
17422            let item2 = item2.clone();
17423            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17424        }
17425    })
17426    .next()
17427    .await;
17428
17429    cx.condition(|editor, _| editor.context_menu_visible())
17430        .await;
17431    cx.update_editor(|editor, _, _| {
17432        let context_menu = editor.context_menu.borrow_mut();
17433        let context_menu = context_menu
17434            .as_ref()
17435            .expect("Should have the context menu deployed");
17436        match context_menu {
17437            CodeContextMenu::Completions(completions_menu) => {
17438                let completions = completions_menu.completions.borrow_mut();
17439                assert_eq!(
17440                    completions
17441                        .iter()
17442                        .map(|completion| &completion.label.text)
17443                        .collect::<Vec<_>>(),
17444                    vec!["method id()", "other"]
17445                )
17446            }
17447            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17448        }
17449    });
17450
17451    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17452        let item1 = item1.clone();
17453        move |_, item_to_resolve, _| {
17454            let item1 = item1.clone();
17455            async move {
17456                if item1 == item_to_resolve {
17457                    Ok(lsp::CompletionItem {
17458                        label: "method id()".to_string(),
17459                        filter_text: Some("id".to_string()),
17460                        detail: Some("Now resolved!".to_string()),
17461                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17462                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17463                            range: lsp::Range::new(
17464                                lsp::Position::new(0, 22),
17465                                lsp::Position::new(0, 22),
17466                            ),
17467                            new_text: ".id".to_string(),
17468                        })),
17469                        ..lsp::CompletionItem::default()
17470                    })
17471                } else {
17472                    Ok(item_to_resolve)
17473                }
17474            }
17475        }
17476    })
17477    .next()
17478    .await
17479    .unwrap();
17480    cx.run_until_parked();
17481
17482    cx.update_editor(|editor, window, cx| {
17483        editor.context_menu_next(&Default::default(), window, cx);
17484    });
17485
17486    cx.update_editor(|editor, _, _| {
17487        let context_menu = editor.context_menu.borrow_mut();
17488        let context_menu = context_menu
17489            .as_ref()
17490            .expect("Should have the context menu deployed");
17491        match context_menu {
17492            CodeContextMenu::Completions(completions_menu) => {
17493                let completions = completions_menu.completions.borrow_mut();
17494                assert_eq!(
17495                    completions
17496                        .iter()
17497                        .map(|completion| &completion.label.text)
17498                        .collect::<Vec<_>>(),
17499                    vec!["method id() Now resolved!", "other"],
17500                    "Should update first completion label, but not second as the filter text did not match."
17501                );
17502            }
17503            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17504        }
17505    });
17506}
17507
17508#[gpui::test]
17509async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17510    init_test(cx, |_| {});
17511    let mut cx = EditorLspTestContext::new_rust(
17512        lsp::ServerCapabilities {
17513            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17514            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17515            completion_provider: Some(lsp::CompletionOptions {
17516                resolve_provider: Some(true),
17517                ..Default::default()
17518            }),
17519            ..Default::default()
17520        },
17521        cx,
17522    )
17523    .await;
17524    cx.set_state(indoc! {"
17525        struct TestStruct {
17526            field: i32
17527        }
17528
17529        fn mainˇ() {
17530            let unused_var = 42;
17531            let test_struct = TestStruct { field: 42 };
17532        }
17533    "});
17534    let symbol_range = cx.lsp_range(indoc! {"
17535        struct TestStruct {
17536            field: i32
17537        }
17538
17539        «fn main»() {
17540            let unused_var = 42;
17541            let test_struct = TestStruct { field: 42 };
17542        }
17543    "});
17544    let mut hover_requests =
17545        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17546            Ok(Some(lsp::Hover {
17547                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17548                    kind: lsp::MarkupKind::Markdown,
17549                    value: "Function documentation".to_string(),
17550                }),
17551                range: Some(symbol_range),
17552            }))
17553        });
17554
17555    // Case 1: Test that code action menu hide hover popover
17556    cx.dispatch_action(Hover);
17557    hover_requests.next().await;
17558    cx.condition(|editor, _| editor.hover_state.visible()).await;
17559    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17560        move |_, _, _| async move {
17561            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17562                lsp::CodeAction {
17563                    title: "Remove unused variable".to_string(),
17564                    kind: Some(CodeActionKind::QUICKFIX),
17565                    edit: Some(lsp::WorkspaceEdit {
17566                        changes: Some(
17567                            [(
17568                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17569                                vec![lsp::TextEdit {
17570                                    range: lsp::Range::new(
17571                                        lsp::Position::new(5, 4),
17572                                        lsp::Position::new(5, 27),
17573                                    ),
17574                                    new_text: "".to_string(),
17575                                }],
17576                            )]
17577                            .into_iter()
17578                            .collect(),
17579                        ),
17580                        ..Default::default()
17581                    }),
17582                    ..Default::default()
17583                },
17584            )]))
17585        },
17586    );
17587    cx.update_editor(|editor, window, cx| {
17588        editor.toggle_code_actions(
17589            &ToggleCodeActions {
17590                deployed_from: None,
17591                quick_launch: false,
17592            },
17593            window,
17594            cx,
17595        );
17596    });
17597    code_action_requests.next().await;
17598    cx.run_until_parked();
17599    cx.condition(|editor, _| editor.context_menu_visible())
17600        .await;
17601    cx.update_editor(|editor, _, _| {
17602        assert!(
17603            !editor.hover_state.visible(),
17604            "Hover popover should be hidden when code action menu is shown"
17605        );
17606        // Hide code actions
17607        editor.context_menu.take();
17608    });
17609
17610    // Case 2: Test that code completions hide hover popover
17611    cx.dispatch_action(Hover);
17612    hover_requests.next().await;
17613    cx.condition(|editor, _| editor.hover_state.visible()).await;
17614    let counter = Arc::new(AtomicUsize::new(0));
17615    let mut completion_requests =
17616        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17617            let counter = counter.clone();
17618            async move {
17619                counter.fetch_add(1, atomic::Ordering::Release);
17620                Ok(Some(lsp::CompletionResponse::Array(vec![
17621                    lsp::CompletionItem {
17622                        label: "main".into(),
17623                        kind: Some(lsp::CompletionItemKind::FUNCTION),
17624                        detail: Some("() -> ()".to_string()),
17625                        ..Default::default()
17626                    },
17627                    lsp::CompletionItem {
17628                        label: "TestStruct".into(),
17629                        kind: Some(lsp::CompletionItemKind::STRUCT),
17630                        detail: Some("struct TestStruct".to_string()),
17631                        ..Default::default()
17632                    },
17633                ])))
17634            }
17635        });
17636    cx.update_editor(|editor, window, cx| {
17637        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
17638    });
17639    completion_requests.next().await;
17640    cx.condition(|editor, _| editor.context_menu_visible())
17641        .await;
17642    cx.update_editor(|editor, _, _| {
17643        assert!(
17644            !editor.hover_state.visible(),
17645            "Hover popover should be hidden when completion menu is shown"
17646        );
17647    });
17648}
17649
17650#[gpui::test]
17651async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
17652    init_test(cx, |_| {});
17653
17654    let mut cx = EditorLspTestContext::new_rust(
17655        lsp::ServerCapabilities {
17656            completion_provider: Some(lsp::CompletionOptions {
17657                trigger_characters: Some(vec![".".to_string()]),
17658                resolve_provider: Some(true),
17659                ..Default::default()
17660            }),
17661            ..Default::default()
17662        },
17663        cx,
17664    )
17665    .await;
17666
17667    cx.set_state("fn main() { let a = 2ˇ; }");
17668    cx.simulate_keystroke(".");
17669
17670    let unresolved_item_1 = lsp::CompletionItem {
17671        label: "id".to_string(),
17672        filter_text: Some("id".to_string()),
17673        detail: None,
17674        documentation: None,
17675        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17676            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17677            new_text: ".id".to_string(),
17678        })),
17679        ..lsp::CompletionItem::default()
17680    };
17681    let resolved_item_1 = lsp::CompletionItem {
17682        additional_text_edits: Some(vec![lsp::TextEdit {
17683            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17684            new_text: "!!".to_string(),
17685        }]),
17686        ..unresolved_item_1.clone()
17687    };
17688    let unresolved_item_2 = lsp::CompletionItem {
17689        label: "other".to_string(),
17690        filter_text: Some("other".to_string()),
17691        detail: None,
17692        documentation: None,
17693        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17694            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17695            new_text: ".other".to_string(),
17696        })),
17697        ..lsp::CompletionItem::default()
17698    };
17699    let resolved_item_2 = lsp::CompletionItem {
17700        additional_text_edits: Some(vec![lsp::TextEdit {
17701            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17702            new_text: "??".to_string(),
17703        }]),
17704        ..unresolved_item_2.clone()
17705    };
17706
17707    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
17708    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
17709    cx.lsp
17710        .server
17711        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17712            let unresolved_item_1 = unresolved_item_1.clone();
17713            let resolved_item_1 = resolved_item_1.clone();
17714            let unresolved_item_2 = unresolved_item_2.clone();
17715            let resolved_item_2 = resolved_item_2.clone();
17716            let resolve_requests_1 = resolve_requests_1.clone();
17717            let resolve_requests_2 = resolve_requests_2.clone();
17718            move |unresolved_request, _| {
17719                let unresolved_item_1 = unresolved_item_1.clone();
17720                let resolved_item_1 = resolved_item_1.clone();
17721                let unresolved_item_2 = unresolved_item_2.clone();
17722                let resolved_item_2 = resolved_item_2.clone();
17723                let resolve_requests_1 = resolve_requests_1.clone();
17724                let resolve_requests_2 = resolve_requests_2.clone();
17725                async move {
17726                    if unresolved_request == unresolved_item_1 {
17727                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
17728                        Ok(resolved_item_1.clone())
17729                    } else if unresolved_request == unresolved_item_2 {
17730                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
17731                        Ok(resolved_item_2.clone())
17732                    } else {
17733                        panic!("Unexpected completion item {unresolved_request:?}")
17734                    }
17735                }
17736            }
17737        })
17738        .detach();
17739
17740    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17741        let unresolved_item_1 = unresolved_item_1.clone();
17742        let unresolved_item_2 = unresolved_item_2.clone();
17743        async move {
17744            Ok(Some(lsp::CompletionResponse::Array(vec![
17745                unresolved_item_1,
17746                unresolved_item_2,
17747            ])))
17748        }
17749    })
17750    .next()
17751    .await;
17752
17753    cx.condition(|editor, _| editor.context_menu_visible())
17754        .await;
17755    cx.update_editor(|editor, _, _| {
17756        let context_menu = editor.context_menu.borrow_mut();
17757        let context_menu = context_menu
17758            .as_ref()
17759            .expect("Should have the context menu deployed");
17760        match context_menu {
17761            CodeContextMenu::Completions(completions_menu) => {
17762                let completions = completions_menu.completions.borrow_mut();
17763                assert_eq!(
17764                    completions
17765                        .iter()
17766                        .map(|completion| &completion.label.text)
17767                        .collect::<Vec<_>>(),
17768                    vec!["id", "other"]
17769                )
17770            }
17771            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17772        }
17773    });
17774    cx.run_until_parked();
17775
17776    cx.update_editor(|editor, window, cx| {
17777        editor.context_menu_next(&ContextMenuNext, window, cx);
17778    });
17779    cx.run_until_parked();
17780    cx.update_editor(|editor, window, cx| {
17781        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17782    });
17783    cx.run_until_parked();
17784    cx.update_editor(|editor, window, cx| {
17785        editor.context_menu_next(&ContextMenuNext, window, cx);
17786    });
17787    cx.run_until_parked();
17788    cx.update_editor(|editor, window, cx| {
17789        editor
17790            .compose_completion(&ComposeCompletion::default(), window, cx)
17791            .expect("No task returned")
17792    })
17793    .await
17794    .expect("Completion failed");
17795    cx.run_until_parked();
17796
17797    cx.update_editor(|editor, _, cx| {
17798        assert_eq!(
17799            resolve_requests_1.load(atomic::Ordering::Acquire),
17800            1,
17801            "Should always resolve once despite multiple selections"
17802        );
17803        assert_eq!(
17804            resolve_requests_2.load(atomic::Ordering::Acquire),
17805            1,
17806            "Should always resolve once after multiple selections and applying the completion"
17807        );
17808        assert_eq!(
17809            editor.text(cx),
17810            "fn main() { let a = ??.other; }",
17811            "Should use resolved data when applying the completion"
17812        );
17813    });
17814}
17815
17816#[gpui::test]
17817async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
17818    init_test(cx, |_| {});
17819
17820    let item_0 = lsp::CompletionItem {
17821        label: "abs".into(),
17822        insert_text: Some("abs".into()),
17823        data: Some(json!({ "very": "special"})),
17824        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
17825        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
17826            lsp::InsertReplaceEdit {
17827                new_text: "abs".to_string(),
17828                insert: lsp::Range::default(),
17829                replace: lsp::Range::default(),
17830            },
17831        )),
17832        ..lsp::CompletionItem::default()
17833    };
17834    let items = iter::once(item_0.clone())
17835        .chain((11..51).map(|i| lsp::CompletionItem {
17836            label: format!("item_{}", i),
17837            insert_text: Some(format!("item_{}", i)),
17838            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
17839            ..lsp::CompletionItem::default()
17840        }))
17841        .collect::<Vec<_>>();
17842
17843    let default_commit_characters = vec!["?".to_string()];
17844    let default_data = json!({ "default": "data"});
17845    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
17846    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
17847    let default_edit_range = lsp::Range {
17848        start: lsp::Position {
17849            line: 0,
17850            character: 5,
17851        },
17852        end: lsp::Position {
17853            line: 0,
17854            character: 5,
17855        },
17856    };
17857
17858    let mut cx = EditorLspTestContext::new_rust(
17859        lsp::ServerCapabilities {
17860            completion_provider: Some(lsp::CompletionOptions {
17861                trigger_characters: Some(vec![".".to_string()]),
17862                resolve_provider: Some(true),
17863                ..Default::default()
17864            }),
17865            ..Default::default()
17866        },
17867        cx,
17868    )
17869    .await;
17870
17871    cx.set_state("fn main() { let a = 2ˇ; }");
17872    cx.simulate_keystroke(".");
17873
17874    let completion_data = default_data.clone();
17875    let completion_characters = default_commit_characters.clone();
17876    let completion_items = items.clone();
17877    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17878        let default_data = completion_data.clone();
17879        let default_commit_characters = completion_characters.clone();
17880        let items = completion_items.clone();
17881        async move {
17882            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
17883                items,
17884                item_defaults: Some(lsp::CompletionListItemDefaults {
17885                    data: Some(default_data.clone()),
17886                    commit_characters: Some(default_commit_characters.clone()),
17887                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
17888                        default_edit_range,
17889                    )),
17890                    insert_text_format: Some(default_insert_text_format),
17891                    insert_text_mode: Some(default_insert_text_mode),
17892                }),
17893                ..lsp::CompletionList::default()
17894            })))
17895        }
17896    })
17897    .next()
17898    .await;
17899
17900    let resolved_items = Arc::new(Mutex::new(Vec::new()));
17901    cx.lsp
17902        .server
17903        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17904            let closure_resolved_items = resolved_items.clone();
17905            move |item_to_resolve, _| {
17906                let closure_resolved_items = closure_resolved_items.clone();
17907                async move {
17908                    closure_resolved_items.lock().push(item_to_resolve.clone());
17909                    Ok(item_to_resolve)
17910                }
17911            }
17912        })
17913        .detach();
17914
17915    cx.condition(|editor, _| editor.context_menu_visible())
17916        .await;
17917    cx.run_until_parked();
17918    cx.update_editor(|editor, _, _| {
17919        let menu = editor.context_menu.borrow_mut();
17920        match menu.as_ref().expect("should have the completions menu") {
17921            CodeContextMenu::Completions(completions_menu) => {
17922                assert_eq!(
17923                    completions_menu
17924                        .entries
17925                        .borrow()
17926                        .iter()
17927                        .map(|mat| mat.string.clone())
17928                        .collect::<Vec<String>>(),
17929                    items
17930                        .iter()
17931                        .map(|completion| completion.label.clone())
17932                        .collect::<Vec<String>>()
17933                );
17934            }
17935            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
17936        }
17937    });
17938    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
17939    // with 4 from the end.
17940    assert_eq!(
17941        *resolved_items.lock(),
17942        [&items[0..16], &items[items.len() - 4..items.len()]]
17943            .concat()
17944            .iter()
17945            .cloned()
17946            .map(|mut item| {
17947                if item.data.is_none() {
17948                    item.data = Some(default_data.clone());
17949                }
17950                item
17951            })
17952            .collect::<Vec<lsp::CompletionItem>>(),
17953        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
17954    );
17955    resolved_items.lock().clear();
17956
17957    cx.update_editor(|editor, window, cx| {
17958        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17959    });
17960    cx.run_until_parked();
17961    // Completions that have already been resolved are skipped.
17962    assert_eq!(
17963        *resolved_items.lock(),
17964        items[items.len() - 17..items.len() - 4]
17965            .iter()
17966            .cloned()
17967            .map(|mut item| {
17968                if item.data.is_none() {
17969                    item.data = Some(default_data.clone());
17970                }
17971                item
17972            })
17973            .collect::<Vec<lsp::CompletionItem>>()
17974    );
17975    resolved_items.lock().clear();
17976}
17977
17978#[gpui::test]
17979async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
17980    init_test(cx, |_| {});
17981
17982    let mut cx = EditorLspTestContext::new(
17983        Language::new(
17984            LanguageConfig {
17985                matcher: LanguageMatcher {
17986                    path_suffixes: vec!["jsx".into()],
17987                    ..Default::default()
17988                },
17989                overrides: [(
17990                    "element".into(),
17991                    LanguageConfigOverride {
17992                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
17993                        ..Default::default()
17994                    },
17995                )]
17996                .into_iter()
17997                .collect(),
17998                ..Default::default()
17999            },
18000            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
18001        )
18002        .with_override_query("(jsx_self_closing_element) @element")
18003        .unwrap(),
18004        lsp::ServerCapabilities {
18005            completion_provider: Some(lsp::CompletionOptions {
18006                trigger_characters: Some(vec![":".to_string()]),
18007                ..Default::default()
18008            }),
18009            ..Default::default()
18010        },
18011        cx,
18012    )
18013    .await;
18014
18015    cx.lsp
18016        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
18017            Ok(Some(lsp::CompletionResponse::Array(vec![
18018                lsp::CompletionItem {
18019                    label: "bg-blue".into(),
18020                    ..Default::default()
18021                },
18022                lsp::CompletionItem {
18023                    label: "bg-red".into(),
18024                    ..Default::default()
18025                },
18026                lsp::CompletionItem {
18027                    label: "bg-yellow".into(),
18028                    ..Default::default()
18029                },
18030            ])))
18031        });
18032
18033    cx.set_state(r#"<p class="bgˇ" />"#);
18034
18035    // Trigger completion when typing a dash, because the dash is an extra
18036    // word character in the 'element' scope, which contains the cursor.
18037    cx.simulate_keystroke("-");
18038    cx.executor().run_until_parked();
18039    cx.update_editor(|editor, _, _| {
18040        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18041        {
18042            assert_eq!(
18043                completion_menu_entries(menu),
18044                &["bg-blue", "bg-red", "bg-yellow"]
18045            );
18046        } else {
18047            panic!("expected completion menu to be open");
18048        }
18049    });
18050
18051    cx.simulate_keystroke("l");
18052    cx.executor().run_until_parked();
18053    cx.update_editor(|editor, _, _| {
18054        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18055        {
18056            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
18057        } else {
18058            panic!("expected completion menu to be open");
18059        }
18060    });
18061
18062    // When filtering completions, consider the character after the '-' to
18063    // be the start of a subword.
18064    cx.set_state(r#"<p class="yelˇ" />"#);
18065    cx.simulate_keystroke("l");
18066    cx.executor().run_until_parked();
18067    cx.update_editor(|editor, _, _| {
18068        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18069        {
18070            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
18071        } else {
18072            panic!("expected completion menu to be open");
18073        }
18074    });
18075}
18076
18077fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
18078    let entries = menu.entries.borrow();
18079    entries.iter().map(|mat| mat.string.clone()).collect()
18080}
18081
18082#[gpui::test]
18083async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
18084    init_test(cx, |settings| {
18085        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
18086            Formatter::Prettier,
18087        )))
18088    });
18089
18090    let fs = FakeFs::new(cx.executor());
18091    fs.insert_file(path!("/file.ts"), Default::default()).await;
18092
18093    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
18094    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18095
18096    language_registry.add(Arc::new(Language::new(
18097        LanguageConfig {
18098            name: "TypeScript".into(),
18099            matcher: LanguageMatcher {
18100                path_suffixes: vec!["ts".to_string()],
18101                ..Default::default()
18102            },
18103            ..Default::default()
18104        },
18105        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18106    )));
18107    update_test_language_settings(cx, |settings| {
18108        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
18109    });
18110
18111    let test_plugin = "test_plugin";
18112    let _ = language_registry.register_fake_lsp(
18113        "TypeScript",
18114        FakeLspAdapter {
18115            prettier_plugins: vec![test_plugin],
18116            ..Default::default()
18117        },
18118    );
18119
18120    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
18121    let buffer = project
18122        .update(cx, |project, cx| {
18123            project.open_local_buffer(path!("/file.ts"), cx)
18124        })
18125        .await
18126        .unwrap();
18127
18128    let buffer_text = "one\ntwo\nthree\n";
18129    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18130    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18131    editor.update_in(cx, |editor, window, cx| {
18132        editor.set_text(buffer_text, window, cx)
18133    });
18134
18135    editor
18136        .update_in(cx, |editor, window, cx| {
18137            editor.perform_format(
18138                project.clone(),
18139                FormatTrigger::Manual,
18140                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18141                window,
18142                cx,
18143            )
18144        })
18145        .unwrap()
18146        .await;
18147    assert_eq!(
18148        editor.update(cx, |editor, cx| editor.text(cx)),
18149        buffer_text.to_string() + prettier_format_suffix,
18150        "Test prettier formatting was not applied to the original buffer text",
18151    );
18152
18153    update_test_language_settings(cx, |settings| {
18154        settings.defaults.formatter = Some(SelectedFormatter::Auto)
18155    });
18156    let format = editor.update_in(cx, |editor, window, cx| {
18157        editor.perform_format(
18158            project.clone(),
18159            FormatTrigger::Manual,
18160            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18161            window,
18162            cx,
18163        )
18164    });
18165    format.await.unwrap();
18166    assert_eq!(
18167        editor.update(cx, |editor, cx| editor.text(cx)),
18168        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18169        "Autoformatting (via test prettier) was not applied to the original buffer text",
18170    );
18171}
18172
18173#[gpui::test]
18174async fn test_addition_reverts(cx: &mut TestAppContext) {
18175    init_test(cx, |_| {});
18176    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18177    let base_text = indoc! {r#"
18178        struct Row;
18179        struct Row1;
18180        struct Row2;
18181
18182        struct Row4;
18183        struct Row5;
18184        struct Row6;
18185
18186        struct Row8;
18187        struct Row9;
18188        struct Row10;"#};
18189
18190    // When addition hunks are not adjacent to carets, no hunk revert is performed
18191    assert_hunk_revert(
18192        indoc! {r#"struct Row;
18193                   struct Row1;
18194                   struct Row1.1;
18195                   struct Row1.2;
18196                   struct Row2;ˇ
18197
18198                   struct Row4;
18199                   struct Row5;
18200                   struct Row6;
18201
18202                   struct Row8;
18203                   ˇstruct Row9;
18204                   struct Row9.1;
18205                   struct Row9.2;
18206                   struct Row9.3;
18207                   struct Row10;"#},
18208        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18209        indoc! {r#"struct Row;
18210                   struct Row1;
18211                   struct Row1.1;
18212                   struct Row1.2;
18213                   struct Row2;ˇ
18214
18215                   struct Row4;
18216                   struct Row5;
18217                   struct Row6;
18218
18219                   struct Row8;
18220                   ˇstruct Row9;
18221                   struct Row9.1;
18222                   struct Row9.2;
18223                   struct Row9.3;
18224                   struct Row10;"#},
18225        base_text,
18226        &mut cx,
18227    );
18228    // Same for selections
18229    assert_hunk_revert(
18230        indoc! {r#"struct Row;
18231                   struct Row1;
18232                   struct Row2;
18233                   struct Row2.1;
18234                   struct Row2.2;
18235                   «ˇ
18236                   struct Row4;
18237                   struct» Row5;
18238                   «struct Row6;
18239                   ˇ»
18240                   struct Row9.1;
18241                   struct Row9.2;
18242                   struct Row9.3;
18243                   struct Row8;
18244                   struct Row9;
18245                   struct Row10;"#},
18246        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18247        indoc! {r#"struct Row;
18248                   struct Row1;
18249                   struct Row2;
18250                   struct Row2.1;
18251                   struct Row2.2;
18252                   «ˇ
18253                   struct Row4;
18254                   struct» Row5;
18255                   «struct Row6;
18256                   ˇ»
18257                   struct Row9.1;
18258                   struct Row9.2;
18259                   struct Row9.3;
18260                   struct Row8;
18261                   struct Row9;
18262                   struct Row10;"#},
18263        base_text,
18264        &mut cx,
18265    );
18266
18267    // When carets and selections intersect the addition hunks, those are reverted.
18268    // Adjacent carets got merged.
18269    assert_hunk_revert(
18270        indoc! {r#"struct Row;
18271                   ˇ// something on the top
18272                   struct Row1;
18273                   struct Row2;
18274                   struct Roˇw3.1;
18275                   struct Row2.2;
18276                   struct Row2.3;ˇ
18277
18278                   struct Row4;
18279                   struct ˇRow5.1;
18280                   struct Row5.2;
18281                   struct «Rowˇ»5.3;
18282                   struct Row5;
18283                   struct Row6;
18284                   ˇ
18285                   struct Row9.1;
18286                   struct «Rowˇ»9.2;
18287                   struct «ˇRow»9.3;
18288                   struct Row8;
18289                   struct Row9;
18290                   «ˇ// something on bottom»
18291                   struct Row10;"#},
18292        vec![
18293            DiffHunkStatusKind::Added,
18294            DiffHunkStatusKind::Added,
18295            DiffHunkStatusKind::Added,
18296            DiffHunkStatusKind::Added,
18297            DiffHunkStatusKind::Added,
18298        ],
18299        indoc! {r#"struct Row;
18300                   ˇstruct Row1;
18301                   struct Row2;
18302                   ˇ
18303                   struct Row4;
18304                   ˇstruct Row5;
18305                   struct Row6;
18306                   ˇ
18307                   ˇstruct Row8;
18308                   struct Row9;
18309                   ˇstruct Row10;"#},
18310        base_text,
18311        &mut cx,
18312    );
18313}
18314
18315#[gpui::test]
18316async fn test_modification_reverts(cx: &mut TestAppContext) {
18317    init_test(cx, |_| {});
18318    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18319    let base_text = indoc! {r#"
18320        struct Row;
18321        struct Row1;
18322        struct Row2;
18323
18324        struct Row4;
18325        struct Row5;
18326        struct Row6;
18327
18328        struct Row8;
18329        struct Row9;
18330        struct Row10;"#};
18331
18332    // Modification hunks behave the same as the addition ones.
18333    assert_hunk_revert(
18334        indoc! {r#"struct Row;
18335                   struct Row1;
18336                   struct Row33;
18337                   ˇ
18338                   struct Row4;
18339                   struct Row5;
18340                   struct Row6;
18341                   ˇ
18342                   struct Row99;
18343                   struct Row9;
18344                   struct Row10;"#},
18345        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18346        indoc! {r#"struct Row;
18347                   struct Row1;
18348                   struct Row33;
18349                   ˇ
18350                   struct Row4;
18351                   struct Row5;
18352                   struct Row6;
18353                   ˇ
18354                   struct Row99;
18355                   struct Row9;
18356                   struct Row10;"#},
18357        base_text,
18358        &mut cx,
18359    );
18360    assert_hunk_revert(
18361        indoc! {r#"struct Row;
18362                   struct Row1;
18363                   struct Row33;
18364                   «ˇ
18365                   struct Row4;
18366                   struct» Row5;
18367                   «struct Row6;
18368                   ˇ»
18369                   struct Row99;
18370                   struct Row9;
18371                   struct Row10;"#},
18372        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18373        indoc! {r#"struct Row;
18374                   struct Row1;
18375                   struct Row33;
18376                   «ˇ
18377                   struct Row4;
18378                   struct» Row5;
18379                   «struct Row6;
18380                   ˇ»
18381                   struct Row99;
18382                   struct Row9;
18383                   struct Row10;"#},
18384        base_text,
18385        &mut cx,
18386    );
18387
18388    assert_hunk_revert(
18389        indoc! {r#"ˇstruct Row1.1;
18390                   struct Row1;
18391                   «ˇstr»uct Row22;
18392
18393                   struct ˇRow44;
18394                   struct Row5;
18395                   struct «Rˇ»ow66;ˇ
18396
18397                   «struˇ»ct Row88;
18398                   struct Row9;
18399                   struct Row1011;ˇ"#},
18400        vec![
18401            DiffHunkStatusKind::Modified,
18402            DiffHunkStatusKind::Modified,
18403            DiffHunkStatusKind::Modified,
18404            DiffHunkStatusKind::Modified,
18405            DiffHunkStatusKind::Modified,
18406            DiffHunkStatusKind::Modified,
18407        ],
18408        indoc! {r#"struct Row;
18409                   ˇstruct Row1;
18410                   struct Row2;
18411                   ˇ
18412                   struct Row4;
18413                   ˇstruct Row5;
18414                   struct Row6;
18415                   ˇ
18416                   struct Row8;
18417                   ˇstruct Row9;
18418                   struct Row10;ˇ"#},
18419        base_text,
18420        &mut cx,
18421    );
18422}
18423
18424#[gpui::test]
18425async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18426    init_test(cx, |_| {});
18427    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18428    let base_text = indoc! {r#"
18429        one
18430
18431        two
18432        three
18433        "#};
18434
18435    cx.set_head_text(base_text);
18436    cx.set_state("\nˇ\n");
18437    cx.executor().run_until_parked();
18438    cx.update_editor(|editor, _window, cx| {
18439        editor.expand_selected_diff_hunks(cx);
18440    });
18441    cx.executor().run_until_parked();
18442    cx.update_editor(|editor, window, cx| {
18443        editor.backspace(&Default::default(), window, cx);
18444    });
18445    cx.run_until_parked();
18446    cx.assert_state_with_diff(
18447        indoc! {r#"
18448
18449        - two
18450        - threeˇ
18451        +
18452        "#}
18453        .to_string(),
18454    );
18455}
18456
18457#[gpui::test]
18458async fn test_deletion_reverts(cx: &mut TestAppContext) {
18459    init_test(cx, |_| {});
18460    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18461    let base_text = indoc! {r#"struct Row;
18462struct Row1;
18463struct Row2;
18464
18465struct Row4;
18466struct Row5;
18467struct Row6;
18468
18469struct Row8;
18470struct Row9;
18471struct Row10;"#};
18472
18473    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18474    assert_hunk_revert(
18475        indoc! {r#"struct Row;
18476                   struct Row2;
18477
18478                   ˇstruct Row4;
18479                   struct Row5;
18480                   struct Row6;
18481                   ˇ
18482                   struct Row8;
18483                   struct Row10;"#},
18484        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18485        indoc! {r#"struct Row;
18486                   struct Row2;
18487
18488                   ˇstruct Row4;
18489                   struct Row5;
18490                   struct Row6;
18491                   ˇ
18492                   struct Row8;
18493                   struct Row10;"#},
18494        base_text,
18495        &mut cx,
18496    );
18497    assert_hunk_revert(
18498        indoc! {r#"struct Row;
18499                   struct Row2;
18500
18501                   «ˇstruct Row4;
18502                   struct» Row5;
18503                   «struct Row6;
18504                   ˇ»
18505                   struct Row8;
18506                   struct Row10;"#},
18507        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18508        indoc! {r#"struct Row;
18509                   struct Row2;
18510
18511                   «ˇstruct Row4;
18512                   struct» Row5;
18513                   «struct Row6;
18514                   ˇ»
18515                   struct Row8;
18516                   struct Row10;"#},
18517        base_text,
18518        &mut cx,
18519    );
18520
18521    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18522    assert_hunk_revert(
18523        indoc! {r#"struct Row;
18524                   ˇstruct Row2;
18525
18526                   struct Row4;
18527                   struct Row5;
18528                   struct Row6;
18529
18530                   struct Row8;ˇ
18531                   struct Row10;"#},
18532        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18533        indoc! {r#"struct Row;
18534                   struct Row1;
18535                   ˇstruct Row2;
18536
18537                   struct Row4;
18538                   struct Row5;
18539                   struct Row6;
18540
18541                   struct Row8;ˇ
18542                   struct Row9;
18543                   struct Row10;"#},
18544        base_text,
18545        &mut cx,
18546    );
18547    assert_hunk_revert(
18548        indoc! {r#"struct Row;
18549                   struct Row2«ˇ;
18550                   struct Row4;
18551                   struct» Row5;
18552                   «struct Row6;
18553
18554                   struct Row8;ˇ»
18555                   struct Row10;"#},
18556        vec![
18557            DiffHunkStatusKind::Deleted,
18558            DiffHunkStatusKind::Deleted,
18559            DiffHunkStatusKind::Deleted,
18560        ],
18561        indoc! {r#"struct Row;
18562                   struct Row1;
18563                   struct Row2«ˇ;
18564
18565                   struct Row4;
18566                   struct» Row5;
18567                   «struct Row6;
18568
18569                   struct Row8;ˇ»
18570                   struct Row9;
18571                   struct Row10;"#},
18572        base_text,
18573        &mut cx,
18574    );
18575}
18576
18577#[gpui::test]
18578async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18579    init_test(cx, |_| {});
18580
18581    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18582    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18583    let base_text_3 =
18584        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18585
18586    let text_1 = edit_first_char_of_every_line(base_text_1);
18587    let text_2 = edit_first_char_of_every_line(base_text_2);
18588    let text_3 = edit_first_char_of_every_line(base_text_3);
18589
18590    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18591    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18592    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18593
18594    let multibuffer = cx.new(|cx| {
18595        let mut multibuffer = MultiBuffer::new(ReadWrite);
18596        multibuffer.push_excerpts(
18597            buffer_1.clone(),
18598            [
18599                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18600                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18601                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18602            ],
18603            cx,
18604        );
18605        multibuffer.push_excerpts(
18606            buffer_2.clone(),
18607            [
18608                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18609                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18610                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18611            ],
18612            cx,
18613        );
18614        multibuffer.push_excerpts(
18615            buffer_3.clone(),
18616            [
18617                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18618                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18619                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18620            ],
18621            cx,
18622        );
18623        multibuffer
18624    });
18625
18626    let fs = FakeFs::new(cx.executor());
18627    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
18628    let (editor, cx) = cx
18629        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
18630    editor.update_in(cx, |editor, _window, cx| {
18631        for (buffer, diff_base) in [
18632            (buffer_1.clone(), base_text_1),
18633            (buffer_2.clone(), base_text_2),
18634            (buffer_3.clone(), base_text_3),
18635        ] {
18636            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18637            editor
18638                .buffer
18639                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18640        }
18641    });
18642    cx.executor().run_until_parked();
18643
18644    editor.update_in(cx, |editor, window, cx| {
18645        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}");
18646        editor.select_all(&SelectAll, window, cx);
18647        editor.git_restore(&Default::default(), window, cx);
18648    });
18649    cx.executor().run_until_parked();
18650
18651    // When all ranges are selected, all buffer hunks are reverted.
18652    editor.update(cx, |editor, cx| {
18653        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");
18654    });
18655    buffer_1.update(cx, |buffer, _| {
18656        assert_eq!(buffer.text(), base_text_1);
18657    });
18658    buffer_2.update(cx, |buffer, _| {
18659        assert_eq!(buffer.text(), base_text_2);
18660    });
18661    buffer_3.update(cx, |buffer, _| {
18662        assert_eq!(buffer.text(), base_text_3);
18663    });
18664
18665    editor.update_in(cx, |editor, window, cx| {
18666        editor.undo(&Default::default(), window, cx);
18667    });
18668
18669    editor.update_in(cx, |editor, window, cx| {
18670        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18671            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
18672        });
18673        editor.git_restore(&Default::default(), window, cx);
18674    });
18675
18676    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
18677    // but not affect buffer_2 and its related excerpts.
18678    editor.update(cx, |editor, cx| {
18679        assert_eq!(
18680            editor.text(cx),
18681            "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}"
18682        );
18683    });
18684    buffer_1.update(cx, |buffer, _| {
18685        assert_eq!(buffer.text(), base_text_1);
18686    });
18687    buffer_2.update(cx, |buffer, _| {
18688        assert_eq!(
18689            buffer.text(),
18690            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
18691        );
18692    });
18693    buffer_3.update(cx, |buffer, _| {
18694        assert_eq!(
18695            buffer.text(),
18696            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
18697        );
18698    });
18699
18700    fn edit_first_char_of_every_line(text: &str) -> String {
18701        text.split('\n')
18702            .map(|line| format!("X{}", &line[1..]))
18703            .collect::<Vec<_>>()
18704            .join("\n")
18705    }
18706}
18707
18708#[gpui::test]
18709async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
18710    init_test(cx, |_| {});
18711
18712    let cols = 4;
18713    let rows = 10;
18714    let sample_text_1 = sample_text(rows, cols, 'a');
18715    assert_eq!(
18716        sample_text_1,
18717        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
18718    );
18719    let sample_text_2 = sample_text(rows, cols, 'l');
18720    assert_eq!(
18721        sample_text_2,
18722        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
18723    );
18724    let sample_text_3 = sample_text(rows, cols, 'v');
18725    assert_eq!(
18726        sample_text_3,
18727        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
18728    );
18729
18730    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
18731    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
18732    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
18733
18734    let multi_buffer = cx.new(|cx| {
18735        let mut multibuffer = MultiBuffer::new(ReadWrite);
18736        multibuffer.push_excerpts(
18737            buffer_1.clone(),
18738            [
18739                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18740                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18741                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18742            ],
18743            cx,
18744        );
18745        multibuffer.push_excerpts(
18746            buffer_2.clone(),
18747            [
18748                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18749                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18750                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18751            ],
18752            cx,
18753        );
18754        multibuffer.push_excerpts(
18755            buffer_3.clone(),
18756            [
18757                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18758                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18759                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18760            ],
18761            cx,
18762        );
18763        multibuffer
18764    });
18765
18766    let fs = FakeFs::new(cx.executor());
18767    fs.insert_tree(
18768        "/a",
18769        json!({
18770            "main.rs": sample_text_1,
18771            "other.rs": sample_text_2,
18772            "lib.rs": sample_text_3,
18773        }),
18774    )
18775    .await;
18776    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18777    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18778    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18779    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18780        Editor::new(
18781            EditorMode::full(),
18782            multi_buffer,
18783            Some(project.clone()),
18784            window,
18785            cx,
18786        )
18787    });
18788    let multibuffer_item_id = workspace
18789        .update(cx, |workspace, window, cx| {
18790            assert!(
18791                workspace.active_item(cx).is_none(),
18792                "active item should be None before the first item is added"
18793            );
18794            workspace.add_item_to_active_pane(
18795                Box::new(multi_buffer_editor.clone()),
18796                None,
18797                true,
18798                window,
18799                cx,
18800            );
18801            let active_item = workspace
18802                .active_item(cx)
18803                .expect("should have an active item after adding the multi buffer");
18804            assert_eq!(
18805                active_item.buffer_kind(cx),
18806                ItemBufferKind::Multibuffer,
18807                "A multi buffer was expected to active after adding"
18808            );
18809            active_item.item_id()
18810        })
18811        .unwrap();
18812    cx.executor().run_until_parked();
18813
18814    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18815        editor.change_selections(
18816            SelectionEffects::scroll(Autoscroll::Next),
18817            window,
18818            cx,
18819            |s| s.select_ranges(Some(1..2)),
18820        );
18821        editor.open_excerpts(&OpenExcerpts, window, cx);
18822    });
18823    cx.executor().run_until_parked();
18824    let first_item_id = workspace
18825        .update(cx, |workspace, window, cx| {
18826            let active_item = workspace
18827                .active_item(cx)
18828                .expect("should have an active item after navigating into the 1st buffer");
18829            let first_item_id = active_item.item_id();
18830            assert_ne!(
18831                first_item_id, multibuffer_item_id,
18832                "Should navigate into the 1st buffer and activate it"
18833            );
18834            assert_eq!(
18835                active_item.buffer_kind(cx),
18836                ItemBufferKind::Singleton,
18837                "New active item should be a singleton buffer"
18838            );
18839            assert_eq!(
18840                active_item
18841                    .act_as::<Editor>(cx)
18842                    .expect("should have navigated into an editor for the 1st buffer")
18843                    .read(cx)
18844                    .text(cx),
18845                sample_text_1
18846            );
18847
18848            workspace
18849                .go_back(workspace.active_pane().downgrade(), window, cx)
18850                .detach_and_log_err(cx);
18851
18852            first_item_id
18853        })
18854        .unwrap();
18855    cx.executor().run_until_parked();
18856    workspace
18857        .update(cx, |workspace, _, cx| {
18858            let active_item = workspace
18859                .active_item(cx)
18860                .expect("should have an active item after navigating back");
18861            assert_eq!(
18862                active_item.item_id(),
18863                multibuffer_item_id,
18864                "Should navigate back to the multi buffer"
18865            );
18866            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
18867        })
18868        .unwrap();
18869
18870    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18871        editor.change_selections(
18872            SelectionEffects::scroll(Autoscroll::Next),
18873            window,
18874            cx,
18875            |s| s.select_ranges(Some(39..40)),
18876        );
18877        editor.open_excerpts(&OpenExcerpts, window, cx);
18878    });
18879    cx.executor().run_until_parked();
18880    let second_item_id = workspace
18881        .update(cx, |workspace, window, cx| {
18882            let active_item = workspace
18883                .active_item(cx)
18884                .expect("should have an active item after navigating into the 2nd buffer");
18885            let second_item_id = active_item.item_id();
18886            assert_ne!(
18887                second_item_id, multibuffer_item_id,
18888                "Should navigate away from the multibuffer"
18889            );
18890            assert_ne!(
18891                second_item_id, first_item_id,
18892                "Should navigate into the 2nd buffer and activate it"
18893            );
18894            assert_eq!(
18895                active_item.buffer_kind(cx),
18896                ItemBufferKind::Singleton,
18897                "New active item should be a singleton buffer"
18898            );
18899            assert_eq!(
18900                active_item
18901                    .act_as::<Editor>(cx)
18902                    .expect("should have navigated into an editor")
18903                    .read(cx)
18904                    .text(cx),
18905                sample_text_2
18906            );
18907
18908            workspace
18909                .go_back(workspace.active_pane().downgrade(), window, cx)
18910                .detach_and_log_err(cx);
18911
18912            second_item_id
18913        })
18914        .unwrap();
18915    cx.executor().run_until_parked();
18916    workspace
18917        .update(cx, |workspace, _, cx| {
18918            let active_item = workspace
18919                .active_item(cx)
18920                .expect("should have an active item after navigating back from the 2nd buffer");
18921            assert_eq!(
18922                active_item.item_id(),
18923                multibuffer_item_id,
18924                "Should navigate back from the 2nd buffer to the multi buffer"
18925            );
18926            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
18927        })
18928        .unwrap();
18929
18930    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18931        editor.change_selections(
18932            SelectionEffects::scroll(Autoscroll::Next),
18933            window,
18934            cx,
18935            |s| s.select_ranges(Some(70..70)),
18936        );
18937        editor.open_excerpts(&OpenExcerpts, window, cx);
18938    });
18939    cx.executor().run_until_parked();
18940    workspace
18941        .update(cx, |workspace, window, cx| {
18942            let active_item = workspace
18943                .active_item(cx)
18944                .expect("should have an active item after navigating into the 3rd buffer");
18945            let third_item_id = active_item.item_id();
18946            assert_ne!(
18947                third_item_id, multibuffer_item_id,
18948                "Should navigate into the 3rd buffer and activate it"
18949            );
18950            assert_ne!(third_item_id, first_item_id);
18951            assert_ne!(third_item_id, second_item_id);
18952            assert_eq!(
18953                active_item.buffer_kind(cx),
18954                ItemBufferKind::Singleton,
18955                "New active item should be a singleton buffer"
18956            );
18957            assert_eq!(
18958                active_item
18959                    .act_as::<Editor>(cx)
18960                    .expect("should have navigated into an editor")
18961                    .read(cx)
18962                    .text(cx),
18963                sample_text_3
18964            );
18965
18966            workspace
18967                .go_back(workspace.active_pane().downgrade(), window, cx)
18968                .detach_and_log_err(cx);
18969        })
18970        .unwrap();
18971    cx.executor().run_until_parked();
18972    workspace
18973        .update(cx, |workspace, _, cx| {
18974            let active_item = workspace
18975                .active_item(cx)
18976                .expect("should have an active item after navigating back from the 3rd buffer");
18977            assert_eq!(
18978                active_item.item_id(),
18979                multibuffer_item_id,
18980                "Should navigate back from the 3rd buffer to the multi buffer"
18981            );
18982            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
18983        })
18984        .unwrap();
18985}
18986
18987#[gpui::test]
18988async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18989    init_test(cx, |_| {});
18990
18991    let mut cx = EditorTestContext::new(cx).await;
18992
18993    let diff_base = r#"
18994        use some::mod;
18995
18996        const A: u32 = 42;
18997
18998        fn main() {
18999            println!("hello");
19000
19001            println!("world");
19002        }
19003        "#
19004    .unindent();
19005
19006    cx.set_state(
19007        &r#"
19008        use some::modified;
19009
19010        ˇ
19011        fn main() {
19012            println!("hello there");
19013
19014            println!("around the");
19015            println!("world");
19016        }
19017        "#
19018        .unindent(),
19019    );
19020
19021    cx.set_head_text(&diff_base);
19022    executor.run_until_parked();
19023
19024    cx.update_editor(|editor, window, cx| {
19025        editor.go_to_next_hunk(&GoToHunk, window, cx);
19026        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19027    });
19028    executor.run_until_parked();
19029    cx.assert_state_with_diff(
19030        r#"
19031          use some::modified;
19032
19033
19034          fn main() {
19035        -     println!("hello");
19036        + ˇ    println!("hello there");
19037
19038              println!("around the");
19039              println!("world");
19040          }
19041        "#
19042        .unindent(),
19043    );
19044
19045    cx.update_editor(|editor, window, cx| {
19046        for _ in 0..2 {
19047            editor.go_to_next_hunk(&GoToHunk, window, cx);
19048            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19049        }
19050    });
19051    executor.run_until_parked();
19052    cx.assert_state_with_diff(
19053        r#"
19054        - use some::mod;
19055        + ˇuse some::modified;
19056
19057
19058          fn main() {
19059        -     println!("hello");
19060        +     println!("hello there");
19061
19062        +     println!("around the");
19063              println!("world");
19064          }
19065        "#
19066        .unindent(),
19067    );
19068
19069    cx.update_editor(|editor, window, cx| {
19070        editor.go_to_next_hunk(&GoToHunk, window, cx);
19071        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19072    });
19073    executor.run_until_parked();
19074    cx.assert_state_with_diff(
19075        r#"
19076        - use some::mod;
19077        + use some::modified;
19078
19079        - const A: u32 = 42;
19080          ˇ
19081          fn main() {
19082        -     println!("hello");
19083        +     println!("hello there");
19084
19085        +     println!("around the");
19086              println!("world");
19087          }
19088        "#
19089        .unindent(),
19090    );
19091
19092    cx.update_editor(|editor, window, cx| {
19093        editor.cancel(&Cancel, window, cx);
19094    });
19095
19096    cx.assert_state_with_diff(
19097        r#"
19098          use some::modified;
19099
19100          ˇ
19101          fn main() {
19102              println!("hello there");
19103
19104              println!("around the");
19105              println!("world");
19106          }
19107        "#
19108        .unindent(),
19109    );
19110}
19111
19112#[gpui::test]
19113async fn test_diff_base_change_with_expanded_diff_hunks(
19114    executor: BackgroundExecutor,
19115    cx: &mut TestAppContext,
19116) {
19117    init_test(cx, |_| {});
19118
19119    let mut cx = EditorTestContext::new(cx).await;
19120
19121    let diff_base = r#"
19122        use some::mod1;
19123        use some::mod2;
19124
19125        const A: u32 = 42;
19126        const B: u32 = 42;
19127        const C: u32 = 42;
19128
19129        fn main() {
19130            println!("hello");
19131
19132            println!("world");
19133        }
19134        "#
19135    .unindent();
19136
19137    cx.set_state(
19138        &r#"
19139        use some::mod2;
19140
19141        const A: u32 = 42;
19142        const C: u32 = 42;
19143
19144        fn main(ˇ) {
19145            //println!("hello");
19146
19147            println!("world");
19148            //
19149            //
19150        }
19151        "#
19152        .unindent(),
19153    );
19154
19155    cx.set_head_text(&diff_base);
19156    executor.run_until_parked();
19157
19158    cx.update_editor(|editor, window, cx| {
19159        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19160    });
19161    executor.run_until_parked();
19162    cx.assert_state_with_diff(
19163        r#"
19164        - use some::mod1;
19165          use some::mod2;
19166
19167          const A: u32 = 42;
19168        - const B: u32 = 42;
19169          const C: u32 = 42;
19170
19171          fn main(ˇ) {
19172        -     println!("hello");
19173        +     //println!("hello");
19174
19175              println!("world");
19176        +     //
19177        +     //
19178          }
19179        "#
19180        .unindent(),
19181    );
19182
19183    cx.set_head_text("new diff base!");
19184    executor.run_until_parked();
19185    cx.assert_state_with_diff(
19186        r#"
19187        - new diff base!
19188        + use some::mod2;
19189        +
19190        + const A: u32 = 42;
19191        + const C: u32 = 42;
19192        +
19193        + fn main(ˇ) {
19194        +     //println!("hello");
19195        +
19196        +     println!("world");
19197        +     //
19198        +     //
19199        + }
19200        "#
19201        .unindent(),
19202    );
19203}
19204
19205#[gpui::test]
19206async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19207    init_test(cx, |_| {});
19208
19209    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19210    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19211    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19212    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19213    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19214    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19215
19216    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19217    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19218    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19219
19220    let multi_buffer = cx.new(|cx| {
19221        let mut multibuffer = MultiBuffer::new(ReadWrite);
19222        multibuffer.push_excerpts(
19223            buffer_1.clone(),
19224            [
19225                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19226                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19227                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19228            ],
19229            cx,
19230        );
19231        multibuffer.push_excerpts(
19232            buffer_2.clone(),
19233            [
19234                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19235                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19236                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19237            ],
19238            cx,
19239        );
19240        multibuffer.push_excerpts(
19241            buffer_3.clone(),
19242            [
19243                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19244                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19245                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19246            ],
19247            cx,
19248        );
19249        multibuffer
19250    });
19251
19252    let editor =
19253        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19254    editor
19255        .update(cx, |editor, _window, cx| {
19256            for (buffer, diff_base) in [
19257                (buffer_1.clone(), file_1_old),
19258                (buffer_2.clone(), file_2_old),
19259                (buffer_3.clone(), file_3_old),
19260            ] {
19261                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19262                editor
19263                    .buffer
19264                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19265            }
19266        })
19267        .unwrap();
19268
19269    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19270    cx.run_until_parked();
19271
19272    cx.assert_editor_state(
19273        &"
19274            ˇaaa
19275            ccc
19276            ddd
19277
19278            ggg
19279            hhh
19280
19281
19282            lll
19283            mmm
19284            NNN
19285
19286            qqq
19287            rrr
19288
19289            uuu
19290            111
19291            222
19292            333
19293
19294            666
19295            777
19296
19297            000
19298            !!!"
19299        .unindent(),
19300    );
19301
19302    cx.update_editor(|editor, window, cx| {
19303        editor.select_all(&SelectAll, window, cx);
19304        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19305    });
19306    cx.executor().run_until_parked();
19307
19308    cx.assert_state_with_diff(
19309        "
19310            «aaa
19311          - bbb
19312            ccc
19313            ddd
19314
19315            ggg
19316            hhh
19317
19318
19319            lll
19320            mmm
19321          - nnn
19322          + NNN
19323
19324            qqq
19325            rrr
19326
19327            uuu
19328            111
19329            222
19330            333
19331
19332          + 666
19333            777
19334
19335            000
19336            !!!ˇ»"
19337            .unindent(),
19338    );
19339}
19340
19341#[gpui::test]
19342async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19343    init_test(cx, |_| {});
19344
19345    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19346    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19347
19348    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19349    let multi_buffer = cx.new(|cx| {
19350        let mut multibuffer = MultiBuffer::new(ReadWrite);
19351        multibuffer.push_excerpts(
19352            buffer.clone(),
19353            [
19354                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19355                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19356                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19357            ],
19358            cx,
19359        );
19360        multibuffer
19361    });
19362
19363    let editor =
19364        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19365    editor
19366        .update(cx, |editor, _window, cx| {
19367            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19368            editor
19369                .buffer
19370                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19371        })
19372        .unwrap();
19373
19374    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19375    cx.run_until_parked();
19376
19377    cx.update_editor(|editor, window, cx| {
19378        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19379    });
19380    cx.executor().run_until_parked();
19381
19382    // When the start of a hunk coincides with the start of its excerpt,
19383    // the hunk is expanded. When the start of a hunk is earlier than
19384    // the start of its excerpt, the hunk is not expanded.
19385    cx.assert_state_with_diff(
19386        "
19387            ˇaaa
19388          - bbb
19389          + BBB
19390
19391          - ddd
19392          - eee
19393          + DDD
19394          + EEE
19395            fff
19396
19397            iii
19398        "
19399        .unindent(),
19400    );
19401}
19402
19403#[gpui::test]
19404async fn test_edits_around_expanded_insertion_hunks(
19405    executor: BackgroundExecutor,
19406    cx: &mut TestAppContext,
19407) {
19408    init_test(cx, |_| {});
19409
19410    let mut cx = EditorTestContext::new(cx).await;
19411
19412    let diff_base = r#"
19413        use some::mod1;
19414        use some::mod2;
19415
19416        const A: u32 = 42;
19417
19418        fn main() {
19419            println!("hello");
19420
19421            println!("world");
19422        }
19423        "#
19424    .unindent();
19425    executor.run_until_parked();
19426    cx.set_state(
19427        &r#"
19428        use some::mod1;
19429        use some::mod2;
19430
19431        const A: u32 = 42;
19432        const B: u32 = 42;
19433        const C: u32 = 42;
19434        ˇ
19435
19436        fn main() {
19437            println!("hello");
19438
19439            println!("world");
19440        }
19441        "#
19442        .unindent(),
19443    );
19444
19445    cx.set_head_text(&diff_base);
19446    executor.run_until_parked();
19447
19448    cx.update_editor(|editor, window, cx| {
19449        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19450    });
19451    executor.run_until_parked();
19452
19453    cx.assert_state_with_diff(
19454        r#"
19455        use some::mod1;
19456        use some::mod2;
19457
19458        const A: u32 = 42;
19459      + const B: u32 = 42;
19460      + const C: u32 = 42;
19461      + ˇ
19462
19463        fn main() {
19464            println!("hello");
19465
19466            println!("world");
19467        }
19468      "#
19469        .unindent(),
19470    );
19471
19472    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19473    executor.run_until_parked();
19474
19475    cx.assert_state_with_diff(
19476        r#"
19477        use some::mod1;
19478        use some::mod2;
19479
19480        const A: u32 = 42;
19481      + const B: u32 = 42;
19482      + const C: u32 = 42;
19483      + const D: u32 = 42;
19484      + ˇ
19485
19486        fn main() {
19487            println!("hello");
19488
19489            println!("world");
19490        }
19491      "#
19492        .unindent(),
19493    );
19494
19495    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19496    executor.run_until_parked();
19497
19498    cx.assert_state_with_diff(
19499        r#"
19500        use some::mod1;
19501        use some::mod2;
19502
19503        const A: u32 = 42;
19504      + const B: u32 = 42;
19505      + const C: u32 = 42;
19506      + const D: u32 = 42;
19507      + const E: u32 = 42;
19508      + ˇ
19509
19510        fn main() {
19511            println!("hello");
19512
19513            println!("world");
19514        }
19515      "#
19516        .unindent(),
19517    );
19518
19519    cx.update_editor(|editor, window, cx| {
19520        editor.delete_line(&DeleteLine, window, cx);
19521    });
19522    executor.run_until_parked();
19523
19524    cx.assert_state_with_diff(
19525        r#"
19526        use some::mod1;
19527        use some::mod2;
19528
19529        const A: u32 = 42;
19530      + const B: u32 = 42;
19531      + const C: u32 = 42;
19532      + const D: u32 = 42;
19533      + const E: u32 = 42;
19534        ˇ
19535        fn main() {
19536            println!("hello");
19537
19538            println!("world");
19539        }
19540      "#
19541        .unindent(),
19542    );
19543
19544    cx.update_editor(|editor, window, cx| {
19545        editor.move_up(&MoveUp, window, cx);
19546        editor.delete_line(&DeleteLine, window, cx);
19547        editor.move_up(&MoveUp, window, cx);
19548        editor.delete_line(&DeleteLine, window, cx);
19549        editor.move_up(&MoveUp, window, cx);
19550        editor.delete_line(&DeleteLine, window, cx);
19551    });
19552    executor.run_until_parked();
19553    cx.assert_state_with_diff(
19554        r#"
19555        use some::mod1;
19556        use some::mod2;
19557
19558        const A: u32 = 42;
19559      + const B: u32 = 42;
19560        ˇ
19561        fn main() {
19562            println!("hello");
19563
19564            println!("world");
19565        }
19566      "#
19567        .unindent(),
19568    );
19569
19570    cx.update_editor(|editor, window, cx| {
19571        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19572        editor.delete_line(&DeleteLine, window, cx);
19573    });
19574    executor.run_until_parked();
19575    cx.assert_state_with_diff(
19576        r#"
19577        ˇ
19578        fn main() {
19579            println!("hello");
19580
19581            println!("world");
19582        }
19583      "#
19584        .unindent(),
19585    );
19586}
19587
19588#[gpui::test]
19589async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19590    init_test(cx, |_| {});
19591
19592    let mut cx = EditorTestContext::new(cx).await;
19593    cx.set_head_text(indoc! { "
19594        one
19595        two
19596        three
19597        four
19598        five
19599        "
19600    });
19601    cx.set_state(indoc! { "
19602        one
19603        ˇthree
19604        five
19605    "});
19606    cx.run_until_parked();
19607    cx.update_editor(|editor, window, cx| {
19608        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19609    });
19610    cx.assert_state_with_diff(
19611        indoc! { "
19612        one
19613      - two
19614        ˇthree
19615      - four
19616        five
19617    "}
19618        .to_string(),
19619    );
19620    cx.update_editor(|editor, window, cx| {
19621        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19622    });
19623
19624    cx.assert_state_with_diff(
19625        indoc! { "
19626        one
19627        ˇthree
19628        five
19629    "}
19630        .to_string(),
19631    );
19632
19633    cx.set_state(indoc! { "
19634        one
19635        ˇTWO
19636        three
19637        four
19638        five
19639    "});
19640    cx.run_until_parked();
19641    cx.update_editor(|editor, window, cx| {
19642        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19643    });
19644
19645    cx.assert_state_with_diff(
19646        indoc! { "
19647            one
19648          - two
19649          + ˇTWO
19650            three
19651            four
19652            five
19653        "}
19654        .to_string(),
19655    );
19656    cx.update_editor(|editor, window, cx| {
19657        editor.move_up(&Default::default(), window, cx);
19658        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19659    });
19660    cx.assert_state_with_diff(
19661        indoc! { "
19662            one
19663            ˇTWO
19664            three
19665            four
19666            five
19667        "}
19668        .to_string(),
19669    );
19670}
19671
19672#[gpui::test]
19673async fn test_edits_around_expanded_deletion_hunks(
19674    executor: BackgroundExecutor,
19675    cx: &mut TestAppContext,
19676) {
19677    init_test(cx, |_| {});
19678
19679    let mut cx = EditorTestContext::new(cx).await;
19680
19681    let diff_base = r#"
19682        use some::mod1;
19683        use some::mod2;
19684
19685        const A: u32 = 42;
19686        const B: u32 = 42;
19687        const C: u32 = 42;
19688
19689
19690        fn main() {
19691            println!("hello");
19692
19693            println!("world");
19694        }
19695    "#
19696    .unindent();
19697    executor.run_until_parked();
19698    cx.set_state(
19699        &r#"
19700        use some::mod1;
19701        use some::mod2;
19702
19703        ˇconst B: u32 = 42;
19704        const C: u32 = 42;
19705
19706
19707        fn main() {
19708            println!("hello");
19709
19710            println!("world");
19711        }
19712        "#
19713        .unindent(),
19714    );
19715
19716    cx.set_head_text(&diff_base);
19717    executor.run_until_parked();
19718
19719    cx.update_editor(|editor, window, cx| {
19720        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19721    });
19722    executor.run_until_parked();
19723
19724    cx.assert_state_with_diff(
19725        r#"
19726        use some::mod1;
19727        use some::mod2;
19728
19729      - const A: u32 = 42;
19730        ˇconst B: u32 = 42;
19731        const C: u32 = 42;
19732
19733
19734        fn main() {
19735            println!("hello");
19736
19737            println!("world");
19738        }
19739      "#
19740        .unindent(),
19741    );
19742
19743    cx.update_editor(|editor, window, cx| {
19744        editor.delete_line(&DeleteLine, window, cx);
19745    });
19746    executor.run_until_parked();
19747    cx.assert_state_with_diff(
19748        r#"
19749        use some::mod1;
19750        use some::mod2;
19751
19752      - const A: u32 = 42;
19753      - const B: u32 = 42;
19754        ˇconst C: u32 = 42;
19755
19756
19757        fn main() {
19758            println!("hello");
19759
19760            println!("world");
19761        }
19762      "#
19763        .unindent(),
19764    );
19765
19766    cx.update_editor(|editor, window, cx| {
19767        editor.delete_line(&DeleteLine, window, cx);
19768    });
19769    executor.run_until_parked();
19770    cx.assert_state_with_diff(
19771        r#"
19772        use some::mod1;
19773        use some::mod2;
19774
19775      - const A: u32 = 42;
19776      - const B: u32 = 42;
19777      - const C: u32 = 42;
19778        ˇ
19779
19780        fn main() {
19781            println!("hello");
19782
19783            println!("world");
19784        }
19785      "#
19786        .unindent(),
19787    );
19788
19789    cx.update_editor(|editor, window, cx| {
19790        editor.handle_input("replacement", window, cx);
19791    });
19792    executor.run_until_parked();
19793    cx.assert_state_with_diff(
19794        r#"
19795        use some::mod1;
19796        use some::mod2;
19797
19798      - const A: u32 = 42;
19799      - const B: u32 = 42;
19800      - const C: u32 = 42;
19801      -
19802      + replacementˇ
19803
19804        fn main() {
19805            println!("hello");
19806
19807            println!("world");
19808        }
19809      "#
19810        .unindent(),
19811    );
19812}
19813
19814#[gpui::test]
19815async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19816    init_test(cx, |_| {});
19817
19818    let mut cx = EditorTestContext::new(cx).await;
19819
19820    let base_text = r#"
19821        one
19822        two
19823        three
19824        four
19825        five
19826    "#
19827    .unindent();
19828    executor.run_until_parked();
19829    cx.set_state(
19830        &r#"
19831        one
19832        two
19833        fˇour
19834        five
19835        "#
19836        .unindent(),
19837    );
19838
19839    cx.set_head_text(&base_text);
19840    executor.run_until_parked();
19841
19842    cx.update_editor(|editor, window, cx| {
19843        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19844    });
19845    executor.run_until_parked();
19846
19847    cx.assert_state_with_diff(
19848        r#"
19849          one
19850          two
19851        - three
19852          fˇour
19853          five
19854        "#
19855        .unindent(),
19856    );
19857
19858    cx.update_editor(|editor, window, cx| {
19859        editor.backspace(&Backspace, window, cx);
19860        editor.backspace(&Backspace, window, cx);
19861    });
19862    executor.run_until_parked();
19863    cx.assert_state_with_diff(
19864        r#"
19865          one
19866          two
19867        - threeˇ
19868        - four
19869        + our
19870          five
19871        "#
19872        .unindent(),
19873    );
19874}
19875
19876#[gpui::test]
19877async fn test_edit_after_expanded_modification_hunk(
19878    executor: BackgroundExecutor,
19879    cx: &mut TestAppContext,
19880) {
19881    init_test(cx, |_| {});
19882
19883    let mut cx = EditorTestContext::new(cx).await;
19884
19885    let diff_base = r#"
19886        use some::mod1;
19887        use some::mod2;
19888
19889        const A: u32 = 42;
19890        const B: u32 = 42;
19891        const C: u32 = 42;
19892        const D: u32 = 42;
19893
19894
19895        fn main() {
19896            println!("hello");
19897
19898            println!("world");
19899        }"#
19900    .unindent();
19901
19902    cx.set_state(
19903        &r#"
19904        use some::mod1;
19905        use some::mod2;
19906
19907        const A: u32 = 42;
19908        const B: u32 = 42;
19909        const C: u32 = 43ˇ
19910        const D: u32 = 42;
19911
19912
19913        fn main() {
19914            println!("hello");
19915
19916            println!("world");
19917        }"#
19918        .unindent(),
19919    );
19920
19921    cx.set_head_text(&diff_base);
19922    executor.run_until_parked();
19923    cx.update_editor(|editor, window, cx| {
19924        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19925    });
19926    executor.run_until_parked();
19927
19928    cx.assert_state_with_diff(
19929        r#"
19930        use some::mod1;
19931        use some::mod2;
19932
19933        const A: u32 = 42;
19934        const B: u32 = 42;
19935      - const C: u32 = 42;
19936      + const C: u32 = 43ˇ
19937        const D: u32 = 42;
19938
19939
19940        fn main() {
19941            println!("hello");
19942
19943            println!("world");
19944        }"#
19945        .unindent(),
19946    );
19947
19948    cx.update_editor(|editor, window, cx| {
19949        editor.handle_input("\nnew_line\n", window, cx);
19950    });
19951    executor.run_until_parked();
19952
19953    cx.assert_state_with_diff(
19954        r#"
19955        use some::mod1;
19956        use some::mod2;
19957
19958        const A: u32 = 42;
19959        const B: u32 = 42;
19960      - const C: u32 = 42;
19961      + const C: u32 = 43
19962      + new_line
19963      + ˇ
19964        const D: u32 = 42;
19965
19966
19967        fn main() {
19968            println!("hello");
19969
19970            println!("world");
19971        }"#
19972        .unindent(),
19973    );
19974}
19975
19976#[gpui::test]
19977async fn test_stage_and_unstage_added_file_hunk(
19978    executor: BackgroundExecutor,
19979    cx: &mut TestAppContext,
19980) {
19981    init_test(cx, |_| {});
19982
19983    let mut cx = EditorTestContext::new(cx).await;
19984    cx.update_editor(|editor, _, cx| {
19985        editor.set_expand_all_diff_hunks(cx);
19986    });
19987
19988    let working_copy = r#"
19989            ˇfn main() {
19990                println!("hello, world!");
19991            }
19992        "#
19993    .unindent();
19994
19995    cx.set_state(&working_copy);
19996    executor.run_until_parked();
19997
19998    cx.assert_state_with_diff(
19999        r#"
20000            + ˇfn main() {
20001            +     println!("hello, world!");
20002            + }
20003        "#
20004        .unindent(),
20005    );
20006    cx.assert_index_text(None);
20007
20008    cx.update_editor(|editor, window, cx| {
20009        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20010    });
20011    executor.run_until_parked();
20012    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
20013    cx.assert_state_with_diff(
20014        r#"
20015            + ˇfn main() {
20016            +     println!("hello, world!");
20017            + }
20018        "#
20019        .unindent(),
20020    );
20021
20022    cx.update_editor(|editor, window, cx| {
20023        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20024    });
20025    executor.run_until_parked();
20026    cx.assert_index_text(None);
20027}
20028
20029async fn setup_indent_guides_editor(
20030    text: &str,
20031    cx: &mut TestAppContext,
20032) -> (BufferId, EditorTestContext) {
20033    init_test(cx, |_| {});
20034
20035    let mut cx = EditorTestContext::new(cx).await;
20036
20037    let buffer_id = cx.update_editor(|editor, window, cx| {
20038        editor.set_text(text, window, cx);
20039        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
20040
20041        buffer_ids[0]
20042    });
20043
20044    (buffer_id, cx)
20045}
20046
20047fn assert_indent_guides(
20048    range: Range<u32>,
20049    expected: Vec<IndentGuide>,
20050    active_indices: Option<Vec<usize>>,
20051    cx: &mut EditorTestContext,
20052) {
20053    let indent_guides = cx.update_editor(|editor, window, cx| {
20054        let snapshot = editor.snapshot(window, cx).display_snapshot;
20055        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
20056            editor,
20057            MultiBufferRow(range.start)..MultiBufferRow(range.end),
20058            true,
20059            &snapshot,
20060            cx,
20061        );
20062
20063        indent_guides.sort_by(|a, b| {
20064            a.depth.cmp(&b.depth).then(
20065                a.start_row
20066                    .cmp(&b.start_row)
20067                    .then(a.end_row.cmp(&b.end_row)),
20068            )
20069        });
20070        indent_guides
20071    });
20072
20073    if let Some(expected) = active_indices {
20074        let active_indices = cx.update_editor(|editor, window, cx| {
20075            let snapshot = editor.snapshot(window, cx).display_snapshot;
20076            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
20077        });
20078
20079        assert_eq!(
20080            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
20081            expected,
20082            "Active indent guide indices do not match"
20083        );
20084    }
20085
20086    assert_eq!(indent_guides, expected, "Indent guides do not match");
20087}
20088
20089fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
20090    IndentGuide {
20091        buffer_id,
20092        start_row: MultiBufferRow(start_row),
20093        end_row: MultiBufferRow(end_row),
20094        depth,
20095        tab_size: 4,
20096        settings: IndentGuideSettings {
20097            enabled: true,
20098            line_width: 1,
20099            active_line_width: 1,
20100            coloring: IndentGuideColoring::default(),
20101            background_coloring: IndentGuideBackgroundColoring::default(),
20102        },
20103    }
20104}
20105
20106#[gpui::test]
20107async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
20108    let (buffer_id, mut cx) = setup_indent_guides_editor(
20109        &"
20110        fn main() {
20111            let a = 1;
20112        }"
20113        .unindent(),
20114        cx,
20115    )
20116    .await;
20117
20118    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20119}
20120
20121#[gpui::test]
20122async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
20123    let (buffer_id, mut cx) = setup_indent_guides_editor(
20124        &"
20125        fn main() {
20126            let a = 1;
20127            let b = 2;
20128        }"
20129        .unindent(),
20130        cx,
20131    )
20132    .await;
20133
20134    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20135}
20136
20137#[gpui::test]
20138async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20139    let (buffer_id, mut cx) = setup_indent_guides_editor(
20140        &"
20141        fn main() {
20142            let a = 1;
20143            if a == 3 {
20144                let b = 2;
20145            } else {
20146                let c = 3;
20147            }
20148        }"
20149        .unindent(),
20150        cx,
20151    )
20152    .await;
20153
20154    assert_indent_guides(
20155        0..8,
20156        vec![
20157            indent_guide(buffer_id, 1, 6, 0),
20158            indent_guide(buffer_id, 3, 3, 1),
20159            indent_guide(buffer_id, 5, 5, 1),
20160        ],
20161        None,
20162        &mut cx,
20163    );
20164}
20165
20166#[gpui::test]
20167async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20168    let (buffer_id, mut cx) = setup_indent_guides_editor(
20169        &"
20170        fn main() {
20171            let a = 1;
20172                let b = 2;
20173            let c = 3;
20174        }"
20175        .unindent(),
20176        cx,
20177    )
20178    .await;
20179
20180    assert_indent_guides(
20181        0..5,
20182        vec![
20183            indent_guide(buffer_id, 1, 3, 0),
20184            indent_guide(buffer_id, 2, 2, 1),
20185        ],
20186        None,
20187        &mut cx,
20188    );
20189}
20190
20191#[gpui::test]
20192async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20193    let (buffer_id, mut cx) = setup_indent_guides_editor(
20194        &"
20195        fn main() {
20196            let a = 1;
20197
20198            let c = 3;
20199        }"
20200        .unindent(),
20201        cx,
20202    )
20203    .await;
20204
20205    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20206}
20207
20208#[gpui::test]
20209async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20210    let (buffer_id, mut cx) = setup_indent_guides_editor(
20211        &"
20212        fn main() {
20213            let a = 1;
20214
20215            let c = 3;
20216
20217            if a == 3 {
20218                let b = 2;
20219            } else {
20220                let c = 3;
20221            }
20222        }"
20223        .unindent(),
20224        cx,
20225    )
20226    .await;
20227
20228    assert_indent_guides(
20229        0..11,
20230        vec![
20231            indent_guide(buffer_id, 1, 9, 0),
20232            indent_guide(buffer_id, 6, 6, 1),
20233            indent_guide(buffer_id, 8, 8, 1),
20234        ],
20235        None,
20236        &mut cx,
20237    );
20238}
20239
20240#[gpui::test]
20241async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20242    let (buffer_id, mut cx) = setup_indent_guides_editor(
20243        &"
20244        fn main() {
20245            let a = 1;
20246
20247            let c = 3;
20248
20249            if a == 3 {
20250                let b = 2;
20251            } else {
20252                let c = 3;
20253            }
20254        }"
20255        .unindent(),
20256        cx,
20257    )
20258    .await;
20259
20260    assert_indent_guides(
20261        1..11,
20262        vec![
20263            indent_guide(buffer_id, 1, 9, 0),
20264            indent_guide(buffer_id, 6, 6, 1),
20265            indent_guide(buffer_id, 8, 8, 1),
20266        ],
20267        None,
20268        &mut cx,
20269    );
20270}
20271
20272#[gpui::test]
20273async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20274    let (buffer_id, mut cx) = setup_indent_guides_editor(
20275        &"
20276        fn main() {
20277            let a = 1;
20278
20279            let c = 3;
20280
20281            if a == 3 {
20282                let b = 2;
20283            } else {
20284                let c = 3;
20285            }
20286        }"
20287        .unindent(),
20288        cx,
20289    )
20290    .await;
20291
20292    assert_indent_guides(
20293        1..10,
20294        vec![
20295            indent_guide(buffer_id, 1, 9, 0),
20296            indent_guide(buffer_id, 6, 6, 1),
20297            indent_guide(buffer_id, 8, 8, 1),
20298        ],
20299        None,
20300        &mut cx,
20301    );
20302}
20303
20304#[gpui::test]
20305async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20306    let (buffer_id, mut cx) = setup_indent_guides_editor(
20307        &"
20308        fn main() {
20309            if a {
20310                b(
20311                    c,
20312                    d,
20313                )
20314            } else {
20315                e(
20316                    f
20317                )
20318            }
20319        }"
20320        .unindent(),
20321        cx,
20322    )
20323    .await;
20324
20325    assert_indent_guides(
20326        0..11,
20327        vec![
20328            indent_guide(buffer_id, 1, 10, 0),
20329            indent_guide(buffer_id, 2, 5, 1),
20330            indent_guide(buffer_id, 7, 9, 1),
20331            indent_guide(buffer_id, 3, 4, 2),
20332            indent_guide(buffer_id, 8, 8, 2),
20333        ],
20334        None,
20335        &mut cx,
20336    );
20337
20338    cx.update_editor(|editor, window, cx| {
20339        editor.fold_at(MultiBufferRow(2), window, cx);
20340        assert_eq!(
20341            editor.display_text(cx),
20342            "
20343            fn main() {
20344                if a {
20345                    b(⋯
20346                    )
20347                } else {
20348                    e(
20349                        f
20350                    )
20351                }
20352            }"
20353            .unindent()
20354        );
20355    });
20356
20357    assert_indent_guides(
20358        0..11,
20359        vec![
20360            indent_guide(buffer_id, 1, 10, 0),
20361            indent_guide(buffer_id, 2, 5, 1),
20362            indent_guide(buffer_id, 7, 9, 1),
20363            indent_guide(buffer_id, 8, 8, 2),
20364        ],
20365        None,
20366        &mut cx,
20367    );
20368}
20369
20370#[gpui::test]
20371async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20372    let (buffer_id, mut cx) = setup_indent_guides_editor(
20373        &"
20374        block1
20375            block2
20376                block3
20377                    block4
20378            block2
20379        block1
20380        block1"
20381            .unindent(),
20382        cx,
20383    )
20384    .await;
20385
20386    assert_indent_guides(
20387        1..10,
20388        vec![
20389            indent_guide(buffer_id, 1, 4, 0),
20390            indent_guide(buffer_id, 2, 3, 1),
20391            indent_guide(buffer_id, 3, 3, 2),
20392        ],
20393        None,
20394        &mut cx,
20395    );
20396}
20397
20398#[gpui::test]
20399async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20400    let (buffer_id, mut cx) = setup_indent_guides_editor(
20401        &"
20402        block1
20403            block2
20404                block3
20405
20406        block1
20407        block1"
20408            .unindent(),
20409        cx,
20410    )
20411    .await;
20412
20413    assert_indent_guides(
20414        0..6,
20415        vec![
20416            indent_guide(buffer_id, 1, 2, 0),
20417            indent_guide(buffer_id, 2, 2, 1),
20418        ],
20419        None,
20420        &mut cx,
20421    );
20422}
20423
20424#[gpui::test]
20425async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20426    let (buffer_id, mut cx) = setup_indent_guides_editor(
20427        &"
20428        function component() {
20429        \treturn (
20430        \t\t\t
20431        \t\t<div>
20432        \t\t\t<abc></abc>
20433        \t\t</div>
20434        \t)
20435        }"
20436        .unindent(),
20437        cx,
20438    )
20439    .await;
20440
20441    assert_indent_guides(
20442        0..8,
20443        vec![
20444            indent_guide(buffer_id, 1, 6, 0),
20445            indent_guide(buffer_id, 2, 5, 1),
20446            indent_guide(buffer_id, 4, 4, 2),
20447        ],
20448        None,
20449        &mut cx,
20450    );
20451}
20452
20453#[gpui::test]
20454async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20455    let (buffer_id, mut cx) = setup_indent_guides_editor(
20456        &"
20457        function component() {
20458        \treturn (
20459        \t
20460        \t\t<div>
20461        \t\t\t<abc></abc>
20462        \t\t</div>
20463        \t)
20464        }"
20465        .unindent(),
20466        cx,
20467    )
20468    .await;
20469
20470    assert_indent_guides(
20471        0..8,
20472        vec![
20473            indent_guide(buffer_id, 1, 6, 0),
20474            indent_guide(buffer_id, 2, 5, 1),
20475            indent_guide(buffer_id, 4, 4, 2),
20476        ],
20477        None,
20478        &mut cx,
20479    );
20480}
20481
20482#[gpui::test]
20483async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20484    let (buffer_id, mut cx) = setup_indent_guides_editor(
20485        &"
20486        block1
20487
20488
20489
20490            block2
20491        "
20492        .unindent(),
20493        cx,
20494    )
20495    .await;
20496
20497    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20498}
20499
20500#[gpui::test]
20501async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20502    let (buffer_id, mut cx) = setup_indent_guides_editor(
20503        &"
20504        def a:
20505        \tb = 3
20506        \tif True:
20507        \t\tc = 4
20508        \t\td = 5
20509        \tprint(b)
20510        "
20511        .unindent(),
20512        cx,
20513    )
20514    .await;
20515
20516    assert_indent_guides(
20517        0..6,
20518        vec![
20519            indent_guide(buffer_id, 1, 5, 0),
20520            indent_guide(buffer_id, 3, 4, 1),
20521        ],
20522        None,
20523        &mut cx,
20524    );
20525}
20526
20527#[gpui::test]
20528async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20529    let (buffer_id, mut cx) = setup_indent_guides_editor(
20530        &"
20531    fn main() {
20532        let a = 1;
20533    }"
20534        .unindent(),
20535        cx,
20536    )
20537    .await;
20538
20539    cx.update_editor(|editor, window, cx| {
20540        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20541            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20542        });
20543    });
20544
20545    assert_indent_guides(
20546        0..3,
20547        vec![indent_guide(buffer_id, 1, 1, 0)],
20548        Some(vec![0]),
20549        &mut cx,
20550    );
20551}
20552
20553#[gpui::test]
20554async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20555    let (buffer_id, mut cx) = setup_indent_guides_editor(
20556        &"
20557    fn main() {
20558        if 1 == 2 {
20559            let a = 1;
20560        }
20561    }"
20562        .unindent(),
20563        cx,
20564    )
20565    .await;
20566
20567    cx.update_editor(|editor, window, cx| {
20568        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20569            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20570        });
20571    });
20572
20573    assert_indent_guides(
20574        0..4,
20575        vec![
20576            indent_guide(buffer_id, 1, 3, 0),
20577            indent_guide(buffer_id, 2, 2, 1),
20578        ],
20579        Some(vec![1]),
20580        &mut cx,
20581    );
20582
20583    cx.update_editor(|editor, window, cx| {
20584        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20585            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20586        });
20587    });
20588
20589    assert_indent_guides(
20590        0..4,
20591        vec![
20592            indent_guide(buffer_id, 1, 3, 0),
20593            indent_guide(buffer_id, 2, 2, 1),
20594        ],
20595        Some(vec![1]),
20596        &mut cx,
20597    );
20598
20599    cx.update_editor(|editor, window, cx| {
20600        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20601            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20602        });
20603    });
20604
20605    assert_indent_guides(
20606        0..4,
20607        vec![
20608            indent_guide(buffer_id, 1, 3, 0),
20609            indent_guide(buffer_id, 2, 2, 1),
20610        ],
20611        Some(vec![0]),
20612        &mut cx,
20613    );
20614}
20615
20616#[gpui::test]
20617async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
20618    let (buffer_id, mut cx) = setup_indent_guides_editor(
20619        &"
20620    fn main() {
20621        let a = 1;
20622
20623        let b = 2;
20624    }"
20625        .unindent(),
20626        cx,
20627    )
20628    .await;
20629
20630    cx.update_editor(|editor, window, cx| {
20631        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20632            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20633        });
20634    });
20635
20636    assert_indent_guides(
20637        0..5,
20638        vec![indent_guide(buffer_id, 1, 3, 0)],
20639        Some(vec![0]),
20640        &mut cx,
20641    );
20642}
20643
20644#[gpui::test]
20645async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
20646    let (buffer_id, mut cx) = setup_indent_guides_editor(
20647        &"
20648    def m:
20649        a = 1
20650        pass"
20651            .unindent(),
20652        cx,
20653    )
20654    .await;
20655
20656    cx.update_editor(|editor, window, cx| {
20657        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20658            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20659        });
20660    });
20661
20662    assert_indent_guides(
20663        0..3,
20664        vec![indent_guide(buffer_id, 1, 2, 0)],
20665        Some(vec![0]),
20666        &mut cx,
20667    );
20668}
20669
20670#[gpui::test]
20671async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
20672    init_test(cx, |_| {});
20673    let mut cx = EditorTestContext::new(cx).await;
20674    let text = indoc! {
20675        "
20676        impl A {
20677            fn b() {
20678                0;
20679                3;
20680                5;
20681                6;
20682                7;
20683            }
20684        }
20685        "
20686    };
20687    let base_text = indoc! {
20688        "
20689        impl A {
20690            fn b() {
20691                0;
20692                1;
20693                2;
20694                3;
20695                4;
20696            }
20697            fn c() {
20698                5;
20699                6;
20700                7;
20701            }
20702        }
20703        "
20704    };
20705
20706    cx.update_editor(|editor, window, cx| {
20707        editor.set_text(text, window, cx);
20708
20709        editor.buffer().update(cx, |multibuffer, cx| {
20710            let buffer = multibuffer.as_singleton().unwrap();
20711            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
20712
20713            multibuffer.set_all_diff_hunks_expanded(cx);
20714            multibuffer.add_diff(diff, cx);
20715
20716            buffer.read(cx).remote_id()
20717        })
20718    });
20719    cx.run_until_parked();
20720
20721    cx.assert_state_with_diff(
20722        indoc! { "
20723          impl A {
20724              fn b() {
20725                  0;
20726        -         1;
20727        -         2;
20728                  3;
20729        -         4;
20730        -     }
20731        -     fn c() {
20732                  5;
20733                  6;
20734                  7;
20735              }
20736          }
20737          ˇ"
20738        }
20739        .to_string(),
20740    );
20741
20742    let mut actual_guides = cx.update_editor(|editor, window, cx| {
20743        editor
20744            .snapshot(window, cx)
20745            .buffer_snapshot()
20746            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
20747            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
20748            .collect::<Vec<_>>()
20749    });
20750    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
20751    assert_eq!(
20752        actual_guides,
20753        vec![
20754            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
20755            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
20756            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
20757        ]
20758    );
20759}
20760
20761#[gpui::test]
20762async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20763    init_test(cx, |_| {});
20764    let mut cx = EditorTestContext::new(cx).await;
20765
20766    let diff_base = r#"
20767        a
20768        b
20769        c
20770        "#
20771    .unindent();
20772
20773    cx.set_state(
20774        &r#"
20775        ˇA
20776        b
20777        C
20778        "#
20779        .unindent(),
20780    );
20781    cx.set_head_text(&diff_base);
20782    cx.update_editor(|editor, window, cx| {
20783        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20784    });
20785    executor.run_until_parked();
20786
20787    let both_hunks_expanded = r#"
20788        - a
20789        + ˇA
20790          b
20791        - c
20792        + C
20793        "#
20794    .unindent();
20795
20796    cx.assert_state_with_diff(both_hunks_expanded.clone());
20797
20798    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20799        let snapshot = editor.snapshot(window, cx);
20800        let hunks = editor
20801            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
20802            .collect::<Vec<_>>();
20803        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20804        let buffer_id = hunks[0].buffer_id;
20805        hunks
20806            .into_iter()
20807            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20808            .collect::<Vec<_>>()
20809    });
20810    assert_eq!(hunk_ranges.len(), 2);
20811
20812    cx.update_editor(|editor, _, cx| {
20813        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20814    });
20815    executor.run_until_parked();
20816
20817    let second_hunk_expanded = r#"
20818          ˇA
20819          b
20820        - c
20821        + C
20822        "#
20823    .unindent();
20824
20825    cx.assert_state_with_diff(second_hunk_expanded);
20826
20827    cx.update_editor(|editor, _, cx| {
20828        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20829    });
20830    executor.run_until_parked();
20831
20832    cx.assert_state_with_diff(both_hunks_expanded.clone());
20833
20834    cx.update_editor(|editor, _, cx| {
20835        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20836    });
20837    executor.run_until_parked();
20838
20839    let first_hunk_expanded = r#"
20840        - a
20841        + ˇA
20842          b
20843          C
20844        "#
20845    .unindent();
20846
20847    cx.assert_state_with_diff(first_hunk_expanded);
20848
20849    cx.update_editor(|editor, _, cx| {
20850        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20851    });
20852    executor.run_until_parked();
20853
20854    cx.assert_state_with_diff(both_hunks_expanded);
20855
20856    cx.set_state(
20857        &r#"
20858        ˇA
20859        b
20860        "#
20861        .unindent(),
20862    );
20863    cx.run_until_parked();
20864
20865    // TODO this cursor position seems bad
20866    cx.assert_state_with_diff(
20867        r#"
20868        - ˇa
20869        + A
20870          b
20871        "#
20872        .unindent(),
20873    );
20874
20875    cx.update_editor(|editor, window, cx| {
20876        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20877    });
20878
20879    cx.assert_state_with_diff(
20880        r#"
20881            - ˇa
20882            + A
20883              b
20884            - c
20885            "#
20886        .unindent(),
20887    );
20888
20889    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20890        let snapshot = editor.snapshot(window, cx);
20891        let hunks = editor
20892            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
20893            .collect::<Vec<_>>();
20894        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20895        let buffer_id = hunks[0].buffer_id;
20896        hunks
20897            .into_iter()
20898            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20899            .collect::<Vec<_>>()
20900    });
20901    assert_eq!(hunk_ranges.len(), 2);
20902
20903    cx.update_editor(|editor, _, cx| {
20904        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20905    });
20906    executor.run_until_parked();
20907
20908    cx.assert_state_with_diff(
20909        r#"
20910        - ˇa
20911        + A
20912          b
20913        "#
20914        .unindent(),
20915    );
20916}
20917
20918#[gpui::test]
20919async fn test_toggle_deletion_hunk_at_start_of_file(
20920    executor: BackgroundExecutor,
20921    cx: &mut TestAppContext,
20922) {
20923    init_test(cx, |_| {});
20924    let mut cx = EditorTestContext::new(cx).await;
20925
20926    let diff_base = r#"
20927        a
20928        b
20929        c
20930        "#
20931    .unindent();
20932
20933    cx.set_state(
20934        &r#"
20935        ˇb
20936        c
20937        "#
20938        .unindent(),
20939    );
20940    cx.set_head_text(&diff_base);
20941    cx.update_editor(|editor, window, cx| {
20942        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20943    });
20944    executor.run_until_parked();
20945
20946    let hunk_expanded = r#"
20947        - a
20948          ˇb
20949          c
20950        "#
20951    .unindent();
20952
20953    cx.assert_state_with_diff(hunk_expanded.clone());
20954
20955    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20956        let snapshot = editor.snapshot(window, cx);
20957        let hunks = editor
20958            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
20959            .collect::<Vec<_>>();
20960        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20961        let buffer_id = hunks[0].buffer_id;
20962        hunks
20963            .into_iter()
20964            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20965            .collect::<Vec<_>>()
20966    });
20967    assert_eq!(hunk_ranges.len(), 1);
20968
20969    cx.update_editor(|editor, _, cx| {
20970        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20971    });
20972    executor.run_until_parked();
20973
20974    let hunk_collapsed = r#"
20975          ˇb
20976          c
20977        "#
20978    .unindent();
20979
20980    cx.assert_state_with_diff(hunk_collapsed);
20981
20982    cx.update_editor(|editor, _, cx| {
20983        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20984    });
20985    executor.run_until_parked();
20986
20987    cx.assert_state_with_diff(hunk_expanded);
20988}
20989
20990#[gpui::test]
20991async fn test_display_diff_hunks(cx: &mut TestAppContext) {
20992    init_test(cx, |_| {});
20993
20994    let fs = FakeFs::new(cx.executor());
20995    fs.insert_tree(
20996        path!("/test"),
20997        json!({
20998            ".git": {},
20999            "file-1": "ONE\n",
21000            "file-2": "TWO\n",
21001            "file-3": "THREE\n",
21002        }),
21003    )
21004    .await;
21005
21006    fs.set_head_for_repo(
21007        path!("/test/.git").as_ref(),
21008        &[
21009            ("file-1", "one\n".into()),
21010            ("file-2", "two\n".into()),
21011            ("file-3", "three\n".into()),
21012        ],
21013        "deadbeef",
21014    );
21015
21016    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
21017    let mut buffers = vec![];
21018    for i in 1..=3 {
21019        let buffer = project
21020            .update(cx, |project, cx| {
21021                let path = format!(path!("/test/file-{}"), i);
21022                project.open_local_buffer(path, cx)
21023            })
21024            .await
21025            .unwrap();
21026        buffers.push(buffer);
21027    }
21028
21029    let multibuffer = cx.new(|cx| {
21030        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
21031        multibuffer.set_all_diff_hunks_expanded(cx);
21032        for buffer in &buffers {
21033            let snapshot = buffer.read(cx).snapshot();
21034            multibuffer.set_excerpts_for_path(
21035                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
21036                buffer.clone(),
21037                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
21038                2,
21039                cx,
21040            );
21041        }
21042        multibuffer
21043    });
21044
21045    let editor = cx.add_window(|window, cx| {
21046        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
21047    });
21048    cx.run_until_parked();
21049
21050    let snapshot = editor
21051        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21052        .unwrap();
21053    let hunks = snapshot
21054        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
21055        .map(|hunk| match hunk {
21056            DisplayDiffHunk::Unfolded {
21057                display_row_range, ..
21058            } => display_row_range,
21059            DisplayDiffHunk::Folded { .. } => unreachable!(),
21060        })
21061        .collect::<Vec<_>>();
21062    assert_eq!(
21063        hunks,
21064        [
21065            DisplayRow(2)..DisplayRow(4),
21066            DisplayRow(7)..DisplayRow(9),
21067            DisplayRow(12)..DisplayRow(14),
21068        ]
21069    );
21070}
21071
21072#[gpui::test]
21073async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
21074    init_test(cx, |_| {});
21075
21076    let mut cx = EditorTestContext::new(cx).await;
21077    cx.set_head_text(indoc! { "
21078        one
21079        two
21080        three
21081        four
21082        five
21083        "
21084    });
21085    cx.set_index_text(indoc! { "
21086        one
21087        two
21088        three
21089        four
21090        five
21091        "
21092    });
21093    cx.set_state(indoc! {"
21094        one
21095        TWO
21096        ˇTHREE
21097        FOUR
21098        five
21099    "});
21100    cx.run_until_parked();
21101    cx.update_editor(|editor, window, cx| {
21102        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21103    });
21104    cx.run_until_parked();
21105    cx.assert_index_text(Some(indoc! {"
21106        one
21107        TWO
21108        THREE
21109        FOUR
21110        five
21111    "}));
21112    cx.set_state(indoc! { "
21113        one
21114        TWO
21115        ˇTHREE-HUNDRED
21116        FOUR
21117        five
21118    "});
21119    cx.run_until_parked();
21120    cx.update_editor(|editor, window, cx| {
21121        let snapshot = editor.snapshot(window, cx);
21122        let hunks = editor
21123            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21124            .collect::<Vec<_>>();
21125        assert_eq!(hunks.len(), 1);
21126        assert_eq!(
21127            hunks[0].status(),
21128            DiffHunkStatus {
21129                kind: DiffHunkStatusKind::Modified,
21130                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21131            }
21132        );
21133
21134        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21135    });
21136    cx.run_until_parked();
21137    cx.assert_index_text(Some(indoc! {"
21138        one
21139        TWO
21140        THREE-HUNDRED
21141        FOUR
21142        five
21143    "}));
21144}
21145
21146#[gpui::test]
21147fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21148    init_test(cx, |_| {});
21149
21150    let editor = cx.add_window(|window, cx| {
21151        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21152        build_editor(buffer, window, cx)
21153    });
21154
21155    let render_args = Arc::new(Mutex::new(None));
21156    let snapshot = editor
21157        .update(cx, |editor, window, cx| {
21158            let snapshot = editor.buffer().read(cx).snapshot(cx);
21159            let range =
21160                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21161
21162            struct RenderArgs {
21163                row: MultiBufferRow,
21164                folded: bool,
21165                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21166            }
21167
21168            let crease = Crease::inline(
21169                range,
21170                FoldPlaceholder::test(),
21171                {
21172                    let toggle_callback = render_args.clone();
21173                    move |row, folded, callback, _window, _cx| {
21174                        *toggle_callback.lock() = Some(RenderArgs {
21175                            row,
21176                            folded,
21177                            callback,
21178                        });
21179                        div()
21180                    }
21181                },
21182                |_row, _folded, _window, _cx| div(),
21183            );
21184
21185            editor.insert_creases(Some(crease), cx);
21186            let snapshot = editor.snapshot(window, cx);
21187            let _div =
21188                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21189            snapshot
21190        })
21191        .unwrap();
21192
21193    let render_args = render_args.lock().take().unwrap();
21194    assert_eq!(render_args.row, MultiBufferRow(1));
21195    assert!(!render_args.folded);
21196    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21197
21198    cx.update_window(*editor, |_, window, cx| {
21199        (render_args.callback)(true, window, cx)
21200    })
21201    .unwrap();
21202    let snapshot = editor
21203        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21204        .unwrap();
21205    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21206
21207    cx.update_window(*editor, |_, window, cx| {
21208        (render_args.callback)(false, window, cx)
21209    })
21210    .unwrap();
21211    let snapshot = editor
21212        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21213        .unwrap();
21214    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21215}
21216
21217#[gpui::test]
21218async fn test_input_text(cx: &mut TestAppContext) {
21219    init_test(cx, |_| {});
21220    let mut cx = EditorTestContext::new(cx).await;
21221
21222    cx.set_state(
21223        &r#"ˇone
21224        two
21225
21226        three
21227        fourˇ
21228        five
21229
21230        siˇx"#
21231            .unindent(),
21232    );
21233
21234    cx.dispatch_action(HandleInput(String::new()));
21235    cx.assert_editor_state(
21236        &r#"ˇone
21237        two
21238
21239        three
21240        fourˇ
21241        five
21242
21243        siˇx"#
21244            .unindent(),
21245    );
21246
21247    cx.dispatch_action(HandleInput("AAAA".to_string()));
21248    cx.assert_editor_state(
21249        &r#"AAAAˇone
21250        two
21251
21252        three
21253        fourAAAAˇ
21254        five
21255
21256        siAAAAˇx"#
21257            .unindent(),
21258    );
21259}
21260
21261#[gpui::test]
21262async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
21263    init_test(cx, |_| {});
21264
21265    let mut cx = EditorTestContext::new(cx).await;
21266    cx.set_state(
21267        r#"let foo = 1;
21268let foo = 2;
21269let foo = 3;
21270let fooˇ = 4;
21271let foo = 5;
21272let foo = 6;
21273let foo = 7;
21274let foo = 8;
21275let foo = 9;
21276let foo = 10;
21277let foo = 11;
21278let foo = 12;
21279let foo = 13;
21280let foo = 14;
21281let foo = 15;"#,
21282    );
21283
21284    cx.update_editor(|e, window, cx| {
21285        assert_eq!(
21286            e.next_scroll_position,
21287            NextScrollCursorCenterTopBottom::Center,
21288            "Default next scroll direction is center",
21289        );
21290
21291        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21292        assert_eq!(
21293            e.next_scroll_position,
21294            NextScrollCursorCenterTopBottom::Top,
21295            "After center, next scroll direction should be top",
21296        );
21297
21298        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21299        assert_eq!(
21300            e.next_scroll_position,
21301            NextScrollCursorCenterTopBottom::Bottom,
21302            "After top, next scroll direction should be bottom",
21303        );
21304
21305        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21306        assert_eq!(
21307            e.next_scroll_position,
21308            NextScrollCursorCenterTopBottom::Center,
21309            "After bottom, scrolling should start over",
21310        );
21311
21312        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21313        assert_eq!(
21314            e.next_scroll_position,
21315            NextScrollCursorCenterTopBottom::Top,
21316            "Scrolling continues if retriggered fast enough"
21317        );
21318    });
21319
21320    cx.executor()
21321        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
21322    cx.executor().run_until_parked();
21323    cx.update_editor(|e, _, _| {
21324        assert_eq!(
21325            e.next_scroll_position,
21326            NextScrollCursorCenterTopBottom::Center,
21327            "If scrolling is not triggered fast enough, it should reset"
21328        );
21329    });
21330}
21331
21332#[gpui::test]
21333async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
21334    init_test(cx, |_| {});
21335    let mut cx = EditorLspTestContext::new_rust(
21336        lsp::ServerCapabilities {
21337            definition_provider: Some(lsp::OneOf::Left(true)),
21338            references_provider: Some(lsp::OneOf::Left(true)),
21339            ..lsp::ServerCapabilities::default()
21340        },
21341        cx,
21342    )
21343    .await;
21344
21345    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21346        let go_to_definition = cx
21347            .lsp
21348            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21349                move |params, _| async move {
21350                    if empty_go_to_definition {
21351                        Ok(None)
21352                    } else {
21353                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21354                            uri: params.text_document_position_params.text_document.uri,
21355                            range: lsp::Range::new(
21356                                lsp::Position::new(4, 3),
21357                                lsp::Position::new(4, 6),
21358                            ),
21359                        })))
21360                    }
21361                },
21362            );
21363        let references = cx
21364            .lsp
21365            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21366                Ok(Some(vec![lsp::Location {
21367                    uri: params.text_document_position.text_document.uri,
21368                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21369                }]))
21370            });
21371        (go_to_definition, references)
21372    };
21373
21374    cx.set_state(
21375        &r#"fn one() {
21376            let mut a = ˇtwo();
21377        }
21378
21379        fn two() {}"#
21380            .unindent(),
21381    );
21382    set_up_lsp_handlers(false, &mut cx);
21383    let navigated = cx
21384        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21385        .await
21386        .expect("Failed to navigate to definition");
21387    assert_eq!(
21388        navigated,
21389        Navigated::Yes,
21390        "Should have navigated to definition from the GetDefinition response"
21391    );
21392    cx.assert_editor_state(
21393        &r#"fn one() {
21394            let mut a = two();
21395        }
21396
21397        fn «twoˇ»() {}"#
21398            .unindent(),
21399    );
21400
21401    let editors = cx.update_workspace(|workspace, _, cx| {
21402        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21403    });
21404    cx.update_editor(|_, _, test_editor_cx| {
21405        assert_eq!(
21406            editors.len(),
21407            1,
21408            "Initially, only one, test, editor should be open in the workspace"
21409        );
21410        assert_eq!(
21411            test_editor_cx.entity(),
21412            editors.last().expect("Asserted len is 1").clone()
21413        );
21414    });
21415
21416    set_up_lsp_handlers(true, &mut cx);
21417    let navigated = cx
21418        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21419        .await
21420        .expect("Failed to navigate to lookup references");
21421    assert_eq!(
21422        navigated,
21423        Navigated::Yes,
21424        "Should have navigated to references as a fallback after empty GoToDefinition response"
21425    );
21426    // We should not change the selections in the existing file,
21427    // if opening another milti buffer with the references
21428    cx.assert_editor_state(
21429        &r#"fn one() {
21430            let mut a = two();
21431        }
21432
21433        fn «twoˇ»() {}"#
21434            .unindent(),
21435    );
21436    let editors = cx.update_workspace(|workspace, _, cx| {
21437        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21438    });
21439    cx.update_editor(|_, _, test_editor_cx| {
21440        assert_eq!(
21441            editors.len(),
21442            2,
21443            "After falling back to references search, we open a new editor with the results"
21444        );
21445        let references_fallback_text = editors
21446            .into_iter()
21447            .find(|new_editor| *new_editor != test_editor_cx.entity())
21448            .expect("Should have one non-test editor now")
21449            .read(test_editor_cx)
21450            .text(test_editor_cx);
21451        assert_eq!(
21452            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21453            "Should use the range from the references response and not the GoToDefinition one"
21454        );
21455    });
21456}
21457
21458#[gpui::test]
21459async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21460    init_test(cx, |_| {});
21461    cx.update(|cx| {
21462        let mut editor_settings = EditorSettings::get_global(cx).clone();
21463        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21464        EditorSettings::override_global(editor_settings, cx);
21465    });
21466    let mut cx = EditorLspTestContext::new_rust(
21467        lsp::ServerCapabilities {
21468            definition_provider: Some(lsp::OneOf::Left(true)),
21469            references_provider: Some(lsp::OneOf::Left(true)),
21470            ..lsp::ServerCapabilities::default()
21471        },
21472        cx,
21473    )
21474    .await;
21475    let original_state = r#"fn one() {
21476        let mut a = ˇtwo();
21477    }
21478
21479    fn two() {}"#
21480        .unindent();
21481    cx.set_state(&original_state);
21482
21483    let mut go_to_definition = cx
21484        .lsp
21485        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21486            move |_, _| async move { Ok(None) },
21487        );
21488    let _references = cx
21489        .lsp
21490        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21491            panic!("Should not call for references with no go to definition fallback")
21492        });
21493
21494    let navigated = cx
21495        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21496        .await
21497        .expect("Failed to navigate to lookup references");
21498    go_to_definition
21499        .next()
21500        .await
21501        .expect("Should have called the go_to_definition handler");
21502
21503    assert_eq!(
21504        navigated,
21505        Navigated::No,
21506        "Should have navigated to references as a fallback after empty GoToDefinition response"
21507    );
21508    cx.assert_editor_state(&original_state);
21509    let editors = cx.update_workspace(|workspace, _, cx| {
21510        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21511    });
21512    cx.update_editor(|_, _, _| {
21513        assert_eq!(
21514            editors.len(),
21515            1,
21516            "After unsuccessful fallback, no other editor should have been opened"
21517        );
21518    });
21519}
21520
21521#[gpui::test]
21522async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
21523    init_test(cx, |_| {});
21524    let mut cx = EditorLspTestContext::new_rust(
21525        lsp::ServerCapabilities {
21526            references_provider: Some(lsp::OneOf::Left(true)),
21527            ..lsp::ServerCapabilities::default()
21528        },
21529        cx,
21530    )
21531    .await;
21532
21533    cx.set_state(
21534        &r#"
21535        fn one() {
21536            let mut a = two();
21537        }
21538
21539        fn ˇtwo() {}"#
21540            .unindent(),
21541    );
21542    cx.lsp
21543        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21544            Ok(Some(vec![
21545                lsp::Location {
21546                    uri: params.text_document_position.text_document.uri.clone(),
21547                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21548                },
21549                lsp::Location {
21550                    uri: params.text_document_position.text_document.uri,
21551                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
21552                },
21553            ]))
21554        });
21555    let navigated = cx
21556        .update_editor(|editor, window, cx| {
21557            editor.find_all_references(&FindAllReferences, window, cx)
21558        })
21559        .unwrap()
21560        .await
21561        .expect("Failed to navigate to references");
21562    assert_eq!(
21563        navigated,
21564        Navigated::Yes,
21565        "Should have navigated to references from the FindAllReferences response"
21566    );
21567    cx.assert_editor_state(
21568        &r#"fn one() {
21569            let mut a = two();
21570        }
21571
21572        fn ˇtwo() {}"#
21573            .unindent(),
21574    );
21575
21576    let editors = cx.update_workspace(|workspace, _, cx| {
21577        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21578    });
21579    cx.update_editor(|_, _, _| {
21580        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
21581    });
21582
21583    cx.set_state(
21584        &r#"fn one() {
21585            let mut a = ˇtwo();
21586        }
21587
21588        fn two() {}"#
21589            .unindent(),
21590    );
21591    let navigated = cx
21592        .update_editor(|editor, window, cx| {
21593            editor.find_all_references(&FindAllReferences, window, cx)
21594        })
21595        .unwrap()
21596        .await
21597        .expect("Failed to navigate to references");
21598    assert_eq!(
21599        navigated,
21600        Navigated::Yes,
21601        "Should have navigated to references from the FindAllReferences response"
21602    );
21603    cx.assert_editor_state(
21604        &r#"fn one() {
21605            let mut a = ˇtwo();
21606        }
21607
21608        fn two() {}"#
21609            .unindent(),
21610    );
21611    let editors = cx.update_workspace(|workspace, _, cx| {
21612        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21613    });
21614    cx.update_editor(|_, _, _| {
21615        assert_eq!(
21616            editors.len(),
21617            2,
21618            "should have re-used the previous multibuffer"
21619        );
21620    });
21621
21622    cx.set_state(
21623        &r#"fn one() {
21624            let mut a = ˇtwo();
21625        }
21626        fn three() {}
21627        fn two() {}"#
21628            .unindent(),
21629    );
21630    cx.lsp
21631        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21632            Ok(Some(vec![
21633                lsp::Location {
21634                    uri: params.text_document_position.text_document.uri.clone(),
21635                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21636                },
21637                lsp::Location {
21638                    uri: params.text_document_position.text_document.uri,
21639                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
21640                },
21641            ]))
21642        });
21643    let navigated = cx
21644        .update_editor(|editor, window, cx| {
21645            editor.find_all_references(&FindAllReferences, window, cx)
21646        })
21647        .unwrap()
21648        .await
21649        .expect("Failed to navigate to references");
21650    assert_eq!(
21651        navigated,
21652        Navigated::Yes,
21653        "Should have navigated to references from the FindAllReferences response"
21654    );
21655    cx.assert_editor_state(
21656        &r#"fn one() {
21657                let mut a = ˇtwo();
21658            }
21659            fn three() {}
21660            fn two() {}"#
21661            .unindent(),
21662    );
21663    let editors = cx.update_workspace(|workspace, _, cx| {
21664        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21665    });
21666    cx.update_editor(|_, _, _| {
21667        assert_eq!(
21668            editors.len(),
21669            3,
21670            "should have used a new multibuffer as offsets changed"
21671        );
21672    });
21673}
21674#[gpui::test]
21675async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
21676    init_test(cx, |_| {});
21677
21678    let language = Arc::new(Language::new(
21679        LanguageConfig::default(),
21680        Some(tree_sitter_rust::LANGUAGE.into()),
21681    ));
21682
21683    let text = r#"
21684        #[cfg(test)]
21685        mod tests() {
21686            #[test]
21687            fn runnable_1() {
21688                let a = 1;
21689            }
21690
21691            #[test]
21692            fn runnable_2() {
21693                let a = 1;
21694                let b = 2;
21695            }
21696        }
21697    "#
21698    .unindent();
21699
21700    let fs = FakeFs::new(cx.executor());
21701    fs.insert_file("/file.rs", Default::default()).await;
21702
21703    let project = Project::test(fs, ["/a".as_ref()], cx).await;
21704    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21705    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21706    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
21707    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
21708
21709    let editor = cx.new_window_entity(|window, cx| {
21710        Editor::new(
21711            EditorMode::full(),
21712            multi_buffer,
21713            Some(project.clone()),
21714            window,
21715            cx,
21716        )
21717    });
21718
21719    editor.update_in(cx, |editor, window, cx| {
21720        let snapshot = editor.buffer().read(cx).snapshot(cx);
21721        editor.tasks.insert(
21722            (buffer.read(cx).remote_id(), 3),
21723            RunnableTasks {
21724                templates: vec![],
21725                offset: snapshot.anchor_before(43),
21726                column: 0,
21727                extra_variables: HashMap::default(),
21728                context_range: BufferOffset(43)..BufferOffset(85),
21729            },
21730        );
21731        editor.tasks.insert(
21732            (buffer.read(cx).remote_id(), 8),
21733            RunnableTasks {
21734                templates: vec![],
21735                offset: snapshot.anchor_before(86),
21736                column: 0,
21737                extra_variables: HashMap::default(),
21738                context_range: BufferOffset(86)..BufferOffset(191),
21739            },
21740        );
21741
21742        // Test finding task when cursor is inside function body
21743        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21744            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
21745        });
21746        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21747        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
21748
21749        // Test finding task when cursor is on function name
21750        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21751            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
21752        });
21753        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21754        assert_eq!(row, 8, "Should find task when cursor is on function name");
21755    });
21756}
21757
21758#[gpui::test]
21759async fn test_folding_buffers(cx: &mut TestAppContext) {
21760    init_test(cx, |_| {});
21761
21762    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21763    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
21764    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
21765
21766    let fs = FakeFs::new(cx.executor());
21767    fs.insert_tree(
21768        path!("/a"),
21769        json!({
21770            "first.rs": sample_text_1,
21771            "second.rs": sample_text_2,
21772            "third.rs": sample_text_3,
21773        }),
21774    )
21775    .await;
21776    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21777    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21778    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21779    let worktree = project.update(cx, |project, cx| {
21780        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21781        assert_eq!(worktrees.len(), 1);
21782        worktrees.pop().unwrap()
21783    });
21784    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21785
21786    let buffer_1 = project
21787        .update(cx, |project, cx| {
21788            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
21789        })
21790        .await
21791        .unwrap();
21792    let buffer_2 = project
21793        .update(cx, |project, cx| {
21794            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
21795        })
21796        .await
21797        .unwrap();
21798    let buffer_3 = project
21799        .update(cx, |project, cx| {
21800            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
21801        })
21802        .await
21803        .unwrap();
21804
21805    let multi_buffer = cx.new(|cx| {
21806        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21807        multi_buffer.push_excerpts(
21808            buffer_1.clone(),
21809            [
21810                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21811                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21812                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21813            ],
21814            cx,
21815        );
21816        multi_buffer.push_excerpts(
21817            buffer_2.clone(),
21818            [
21819                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21820                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21821                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21822            ],
21823            cx,
21824        );
21825        multi_buffer.push_excerpts(
21826            buffer_3.clone(),
21827            [
21828                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21829                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21830                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21831            ],
21832            cx,
21833        );
21834        multi_buffer
21835    });
21836    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21837        Editor::new(
21838            EditorMode::full(),
21839            multi_buffer.clone(),
21840            Some(project.clone()),
21841            window,
21842            cx,
21843        )
21844    });
21845
21846    assert_eq!(
21847        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21848        "\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",
21849    );
21850
21851    multi_buffer_editor.update(cx, |editor, cx| {
21852        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21853    });
21854    assert_eq!(
21855        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21856        "\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",
21857        "After folding the first buffer, its text should not be displayed"
21858    );
21859
21860    multi_buffer_editor.update(cx, |editor, cx| {
21861        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21862    });
21863    assert_eq!(
21864        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21865        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
21866        "After folding the second buffer, its text should not be displayed"
21867    );
21868
21869    multi_buffer_editor.update(cx, |editor, cx| {
21870        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21871    });
21872    assert_eq!(
21873        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21874        "\n\n\n\n\n",
21875        "After folding the third buffer, its text should not be displayed"
21876    );
21877
21878    // Emulate selection inside the fold logic, that should work
21879    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21880        editor
21881            .snapshot(window, cx)
21882            .next_line_boundary(Point::new(0, 4));
21883    });
21884
21885    multi_buffer_editor.update(cx, |editor, cx| {
21886        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21887    });
21888    assert_eq!(
21889        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21890        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21891        "After unfolding the second buffer, its text should be displayed"
21892    );
21893
21894    // Typing inside of buffer 1 causes that buffer to be unfolded.
21895    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21896        assert_eq!(
21897            multi_buffer
21898                .read(cx)
21899                .snapshot(cx)
21900                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
21901                .collect::<String>(),
21902            "bbbb"
21903        );
21904        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21905            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
21906        });
21907        editor.handle_input("B", window, cx);
21908    });
21909
21910    assert_eq!(
21911        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21912        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21913        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
21914    );
21915
21916    multi_buffer_editor.update(cx, |editor, cx| {
21917        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21918    });
21919    assert_eq!(
21920        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21921        "\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",
21922        "After unfolding the all buffers, all original text should be displayed"
21923    );
21924}
21925
21926#[gpui::test]
21927async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
21928    init_test(cx, |_| {});
21929
21930    let sample_text_1 = "1111\n2222\n3333".to_string();
21931    let sample_text_2 = "4444\n5555\n6666".to_string();
21932    let sample_text_3 = "7777\n8888\n9999".to_string();
21933
21934    let fs = FakeFs::new(cx.executor());
21935    fs.insert_tree(
21936        path!("/a"),
21937        json!({
21938            "first.rs": sample_text_1,
21939            "second.rs": sample_text_2,
21940            "third.rs": sample_text_3,
21941        }),
21942    )
21943    .await;
21944    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21945    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21946    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21947    let worktree = project.update(cx, |project, cx| {
21948        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21949        assert_eq!(worktrees.len(), 1);
21950        worktrees.pop().unwrap()
21951    });
21952    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21953
21954    let buffer_1 = project
21955        .update(cx, |project, cx| {
21956            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
21957        })
21958        .await
21959        .unwrap();
21960    let buffer_2 = project
21961        .update(cx, |project, cx| {
21962            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
21963        })
21964        .await
21965        .unwrap();
21966    let buffer_3 = project
21967        .update(cx, |project, cx| {
21968            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
21969        })
21970        .await
21971        .unwrap();
21972
21973    let multi_buffer = cx.new(|cx| {
21974        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21975        multi_buffer.push_excerpts(
21976            buffer_1.clone(),
21977            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21978            cx,
21979        );
21980        multi_buffer.push_excerpts(
21981            buffer_2.clone(),
21982            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21983            cx,
21984        );
21985        multi_buffer.push_excerpts(
21986            buffer_3.clone(),
21987            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21988            cx,
21989        );
21990        multi_buffer
21991    });
21992
21993    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21994        Editor::new(
21995            EditorMode::full(),
21996            multi_buffer,
21997            Some(project.clone()),
21998            window,
21999            cx,
22000        )
22001    });
22002
22003    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
22004    assert_eq!(
22005        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22006        full_text,
22007    );
22008
22009    multi_buffer_editor.update(cx, |editor, cx| {
22010        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22011    });
22012    assert_eq!(
22013        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22014        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
22015        "After folding the first buffer, its text should not be displayed"
22016    );
22017
22018    multi_buffer_editor.update(cx, |editor, cx| {
22019        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22020    });
22021
22022    assert_eq!(
22023        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22024        "\n\n\n\n\n\n7777\n8888\n9999",
22025        "After folding the second buffer, its text should not be displayed"
22026    );
22027
22028    multi_buffer_editor.update(cx, |editor, cx| {
22029        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22030    });
22031    assert_eq!(
22032        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22033        "\n\n\n\n\n",
22034        "After folding the third buffer, its text should not be displayed"
22035    );
22036
22037    multi_buffer_editor.update(cx, |editor, cx| {
22038        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22039    });
22040    assert_eq!(
22041        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22042        "\n\n\n\n4444\n5555\n6666\n\n",
22043        "After unfolding the second buffer, its text should be displayed"
22044    );
22045
22046    multi_buffer_editor.update(cx, |editor, cx| {
22047        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
22048    });
22049    assert_eq!(
22050        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22051        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
22052        "After unfolding the first buffer, its text should be displayed"
22053    );
22054
22055    multi_buffer_editor.update(cx, |editor, cx| {
22056        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22057    });
22058    assert_eq!(
22059        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22060        full_text,
22061        "After unfolding all buffers, all original text should be displayed"
22062    );
22063}
22064
22065#[gpui::test]
22066async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
22067    init_test(cx, |_| {});
22068
22069    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22070
22071    let fs = FakeFs::new(cx.executor());
22072    fs.insert_tree(
22073        path!("/a"),
22074        json!({
22075            "main.rs": sample_text,
22076        }),
22077    )
22078    .await;
22079    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22080    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22081    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22082    let worktree = project.update(cx, |project, cx| {
22083        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22084        assert_eq!(worktrees.len(), 1);
22085        worktrees.pop().unwrap()
22086    });
22087    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22088
22089    let buffer_1 = project
22090        .update(cx, |project, cx| {
22091            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22092        })
22093        .await
22094        .unwrap();
22095
22096    let multi_buffer = cx.new(|cx| {
22097        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22098        multi_buffer.push_excerpts(
22099            buffer_1.clone(),
22100            [ExcerptRange::new(
22101                Point::new(0, 0)
22102                    ..Point::new(
22103                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
22104                        0,
22105                    ),
22106            )],
22107            cx,
22108        );
22109        multi_buffer
22110    });
22111    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22112        Editor::new(
22113            EditorMode::full(),
22114            multi_buffer,
22115            Some(project.clone()),
22116            window,
22117            cx,
22118        )
22119    });
22120
22121    let selection_range = Point::new(1, 0)..Point::new(2, 0);
22122    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22123        enum TestHighlight {}
22124        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22125        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
22126        editor.highlight_text::<TestHighlight>(
22127            vec![highlight_range.clone()],
22128            HighlightStyle::color(Hsla::green()),
22129            cx,
22130        );
22131        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22132            s.select_ranges(Some(highlight_range))
22133        });
22134    });
22135
22136    let full_text = format!("\n\n{sample_text}");
22137    assert_eq!(
22138        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22139        full_text,
22140    );
22141}
22142
22143#[gpui::test]
22144async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22145    init_test(cx, |_| {});
22146    cx.update(|cx| {
22147        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22148            "keymaps/default-linux.json",
22149            cx,
22150        )
22151        .unwrap();
22152        cx.bind_keys(default_key_bindings);
22153    });
22154
22155    let (editor, cx) = cx.add_window_view(|window, cx| {
22156        let multi_buffer = MultiBuffer::build_multi(
22157            [
22158                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22159                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22160                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22161                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22162            ],
22163            cx,
22164        );
22165        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22166
22167        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22168        // fold all but the second buffer, so that we test navigating between two
22169        // adjacent folded buffers, as well as folded buffers at the start and
22170        // end the multibuffer
22171        editor.fold_buffer(buffer_ids[0], cx);
22172        editor.fold_buffer(buffer_ids[2], cx);
22173        editor.fold_buffer(buffer_ids[3], cx);
22174
22175        editor
22176    });
22177    cx.simulate_resize(size(px(1000.), px(1000.)));
22178
22179    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22180    cx.assert_excerpts_with_selections(indoc! {"
22181        [EXCERPT]
22182        ˇ[FOLDED]
22183        [EXCERPT]
22184        a1
22185        b1
22186        [EXCERPT]
22187        [FOLDED]
22188        [EXCERPT]
22189        [FOLDED]
22190        "
22191    });
22192    cx.simulate_keystroke("down");
22193    cx.assert_excerpts_with_selections(indoc! {"
22194        [EXCERPT]
22195        [FOLDED]
22196        [EXCERPT]
22197        ˇa1
22198        b1
22199        [EXCERPT]
22200        [FOLDED]
22201        [EXCERPT]
22202        [FOLDED]
22203        "
22204    });
22205    cx.simulate_keystroke("down");
22206    cx.assert_excerpts_with_selections(indoc! {"
22207        [EXCERPT]
22208        [FOLDED]
22209        [EXCERPT]
22210        a1
22211        ˇb1
22212        [EXCERPT]
22213        [FOLDED]
22214        [EXCERPT]
22215        [FOLDED]
22216        "
22217    });
22218    cx.simulate_keystroke("down");
22219    cx.assert_excerpts_with_selections(indoc! {"
22220        [EXCERPT]
22221        [FOLDED]
22222        [EXCERPT]
22223        a1
22224        b1
22225        ˇ[EXCERPT]
22226        [FOLDED]
22227        [EXCERPT]
22228        [FOLDED]
22229        "
22230    });
22231    cx.simulate_keystroke("down");
22232    cx.assert_excerpts_with_selections(indoc! {"
22233        [EXCERPT]
22234        [FOLDED]
22235        [EXCERPT]
22236        a1
22237        b1
22238        [EXCERPT]
22239        ˇ[FOLDED]
22240        [EXCERPT]
22241        [FOLDED]
22242        "
22243    });
22244    for _ in 0..5 {
22245        cx.simulate_keystroke("down");
22246        cx.assert_excerpts_with_selections(indoc! {"
22247            [EXCERPT]
22248            [FOLDED]
22249            [EXCERPT]
22250            a1
22251            b1
22252            [EXCERPT]
22253            [FOLDED]
22254            [EXCERPT]
22255            ˇ[FOLDED]
22256            "
22257        });
22258    }
22259
22260    cx.simulate_keystroke("up");
22261    cx.assert_excerpts_with_selections(indoc! {"
22262        [EXCERPT]
22263        [FOLDED]
22264        [EXCERPT]
22265        a1
22266        b1
22267        [EXCERPT]
22268        ˇ[FOLDED]
22269        [EXCERPT]
22270        [FOLDED]
22271        "
22272    });
22273    cx.simulate_keystroke("up");
22274    cx.assert_excerpts_with_selections(indoc! {"
22275        [EXCERPT]
22276        [FOLDED]
22277        [EXCERPT]
22278        a1
22279        b1
22280        ˇ[EXCERPT]
22281        [FOLDED]
22282        [EXCERPT]
22283        [FOLDED]
22284        "
22285    });
22286    cx.simulate_keystroke("up");
22287    cx.assert_excerpts_with_selections(indoc! {"
22288        [EXCERPT]
22289        [FOLDED]
22290        [EXCERPT]
22291        a1
22292        ˇb1
22293        [EXCERPT]
22294        [FOLDED]
22295        [EXCERPT]
22296        [FOLDED]
22297        "
22298    });
22299    cx.simulate_keystroke("up");
22300    cx.assert_excerpts_with_selections(indoc! {"
22301        [EXCERPT]
22302        [FOLDED]
22303        [EXCERPT]
22304        ˇa1
22305        b1
22306        [EXCERPT]
22307        [FOLDED]
22308        [EXCERPT]
22309        [FOLDED]
22310        "
22311    });
22312    for _ in 0..5 {
22313        cx.simulate_keystroke("up");
22314        cx.assert_excerpts_with_selections(indoc! {"
22315            [EXCERPT]
22316            ˇ[FOLDED]
22317            [EXCERPT]
22318            a1
22319            b1
22320            [EXCERPT]
22321            [FOLDED]
22322            [EXCERPT]
22323            [FOLDED]
22324            "
22325        });
22326    }
22327}
22328
22329#[gpui::test]
22330async fn test_edit_prediction_text(cx: &mut TestAppContext) {
22331    init_test(cx, |_| {});
22332
22333    // Simple insertion
22334    assert_highlighted_edits(
22335        "Hello, world!",
22336        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
22337        true,
22338        cx,
22339        |highlighted_edits, cx| {
22340            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
22341            assert_eq!(highlighted_edits.highlights.len(), 1);
22342            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
22343            assert_eq!(
22344                highlighted_edits.highlights[0].1.background_color,
22345                Some(cx.theme().status().created_background)
22346            );
22347        },
22348    )
22349    .await;
22350
22351    // Replacement
22352    assert_highlighted_edits(
22353        "This is a test.",
22354        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
22355        false,
22356        cx,
22357        |highlighted_edits, cx| {
22358            assert_eq!(highlighted_edits.text, "That is a test.");
22359            assert_eq!(highlighted_edits.highlights.len(), 1);
22360            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
22361            assert_eq!(
22362                highlighted_edits.highlights[0].1.background_color,
22363                Some(cx.theme().status().created_background)
22364            );
22365        },
22366    )
22367    .await;
22368
22369    // Multiple edits
22370    assert_highlighted_edits(
22371        "Hello, world!",
22372        vec![
22373            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
22374            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
22375        ],
22376        false,
22377        cx,
22378        |highlighted_edits, cx| {
22379            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
22380            assert_eq!(highlighted_edits.highlights.len(), 2);
22381            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
22382            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
22383            assert_eq!(
22384                highlighted_edits.highlights[0].1.background_color,
22385                Some(cx.theme().status().created_background)
22386            );
22387            assert_eq!(
22388                highlighted_edits.highlights[1].1.background_color,
22389                Some(cx.theme().status().created_background)
22390            );
22391        },
22392    )
22393    .await;
22394
22395    // Multiple lines with edits
22396    assert_highlighted_edits(
22397        "First line\nSecond line\nThird line\nFourth line",
22398        vec![
22399            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
22400            (
22401                Point::new(2, 0)..Point::new(2, 10),
22402                "New third line".to_string(),
22403            ),
22404            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
22405        ],
22406        false,
22407        cx,
22408        |highlighted_edits, cx| {
22409            assert_eq!(
22410                highlighted_edits.text,
22411                "Second modified\nNew third line\nFourth updated line"
22412            );
22413            assert_eq!(highlighted_edits.highlights.len(), 3);
22414            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
22415            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
22416            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
22417            for highlight in &highlighted_edits.highlights {
22418                assert_eq!(
22419                    highlight.1.background_color,
22420                    Some(cx.theme().status().created_background)
22421                );
22422            }
22423        },
22424    )
22425    .await;
22426}
22427
22428#[gpui::test]
22429async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
22430    init_test(cx, |_| {});
22431
22432    // Deletion
22433    assert_highlighted_edits(
22434        "Hello, world!",
22435        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
22436        true,
22437        cx,
22438        |highlighted_edits, cx| {
22439            assert_eq!(highlighted_edits.text, "Hello, world!");
22440            assert_eq!(highlighted_edits.highlights.len(), 1);
22441            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
22442            assert_eq!(
22443                highlighted_edits.highlights[0].1.background_color,
22444                Some(cx.theme().status().deleted_background)
22445            );
22446        },
22447    )
22448    .await;
22449
22450    // Insertion
22451    assert_highlighted_edits(
22452        "Hello, world!",
22453        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
22454        true,
22455        cx,
22456        |highlighted_edits, cx| {
22457            assert_eq!(highlighted_edits.highlights.len(), 1);
22458            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
22459            assert_eq!(
22460                highlighted_edits.highlights[0].1.background_color,
22461                Some(cx.theme().status().created_background)
22462            );
22463        },
22464    )
22465    .await;
22466}
22467
22468async fn assert_highlighted_edits(
22469    text: &str,
22470    edits: Vec<(Range<Point>, String)>,
22471    include_deletions: bool,
22472    cx: &mut TestAppContext,
22473    assertion_fn: impl Fn(HighlightedText, &App),
22474) {
22475    let window = cx.add_window(|window, cx| {
22476        let buffer = MultiBuffer::build_simple(text, cx);
22477        Editor::new(EditorMode::full(), buffer, None, window, cx)
22478    });
22479    let cx = &mut VisualTestContext::from_window(*window, cx);
22480
22481    let (buffer, snapshot) = window
22482        .update(cx, |editor, _window, cx| {
22483            (
22484                editor.buffer().clone(),
22485                editor.buffer().read(cx).snapshot(cx),
22486            )
22487        })
22488        .unwrap();
22489
22490    let edits = edits
22491        .into_iter()
22492        .map(|(range, edit)| {
22493            (
22494                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
22495                edit,
22496            )
22497        })
22498        .collect::<Vec<_>>();
22499
22500    let text_anchor_edits = edits
22501        .clone()
22502        .into_iter()
22503        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
22504        .collect::<Vec<_>>();
22505
22506    let edit_preview = window
22507        .update(cx, |_, _window, cx| {
22508            buffer
22509                .read(cx)
22510                .as_singleton()
22511                .unwrap()
22512                .read(cx)
22513                .preview_edits(text_anchor_edits.into(), cx)
22514        })
22515        .unwrap()
22516        .await;
22517
22518    cx.update(|_window, cx| {
22519        let highlighted_edits = edit_prediction_edit_text(
22520            snapshot.as_singleton().unwrap().2,
22521            &edits,
22522            &edit_preview,
22523            include_deletions,
22524            cx,
22525        );
22526        assertion_fn(highlighted_edits, cx)
22527    });
22528}
22529
22530#[track_caller]
22531fn assert_breakpoint(
22532    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
22533    path: &Arc<Path>,
22534    expected: Vec<(u32, Breakpoint)>,
22535) {
22536    if expected.is_empty() {
22537        assert!(!breakpoints.contains_key(path), "{}", path.display());
22538    } else {
22539        let mut breakpoint = breakpoints
22540            .get(path)
22541            .unwrap()
22542            .iter()
22543            .map(|breakpoint| {
22544                (
22545                    breakpoint.row,
22546                    Breakpoint {
22547                        message: breakpoint.message.clone(),
22548                        state: breakpoint.state,
22549                        condition: breakpoint.condition.clone(),
22550                        hit_condition: breakpoint.hit_condition.clone(),
22551                    },
22552                )
22553            })
22554            .collect::<Vec<_>>();
22555
22556        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
22557
22558        assert_eq!(expected, breakpoint);
22559    }
22560}
22561
22562fn add_log_breakpoint_at_cursor(
22563    editor: &mut Editor,
22564    log_message: &str,
22565    window: &mut Window,
22566    cx: &mut Context<Editor>,
22567) {
22568    let (anchor, bp) = editor
22569        .breakpoints_at_cursors(window, cx)
22570        .first()
22571        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
22572        .unwrap_or_else(|| {
22573            let cursor_position: Point = editor.selections.newest(cx).head();
22574
22575            let breakpoint_position = editor
22576                .snapshot(window, cx)
22577                .display_snapshot
22578                .buffer_snapshot()
22579                .anchor_before(Point::new(cursor_position.row, 0));
22580
22581            (breakpoint_position, Breakpoint::new_log(log_message))
22582        });
22583
22584    editor.edit_breakpoint_at_anchor(
22585        anchor,
22586        bp,
22587        BreakpointEditAction::EditLogMessage(log_message.into()),
22588        cx,
22589    );
22590}
22591
22592#[gpui::test]
22593async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
22594    init_test(cx, |_| {});
22595
22596    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22597    let fs = FakeFs::new(cx.executor());
22598    fs.insert_tree(
22599        path!("/a"),
22600        json!({
22601            "main.rs": sample_text,
22602        }),
22603    )
22604    .await;
22605    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22606    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22607    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22608
22609    let fs = FakeFs::new(cx.executor());
22610    fs.insert_tree(
22611        path!("/a"),
22612        json!({
22613            "main.rs": sample_text,
22614        }),
22615    )
22616    .await;
22617    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22618    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22619    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22620    let worktree_id = workspace
22621        .update(cx, |workspace, _window, cx| {
22622            workspace.project().update(cx, |project, cx| {
22623                project.worktrees(cx).next().unwrap().read(cx).id()
22624            })
22625        })
22626        .unwrap();
22627
22628    let buffer = project
22629        .update(cx, |project, cx| {
22630            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22631        })
22632        .await
22633        .unwrap();
22634
22635    let (editor, cx) = cx.add_window_view(|window, cx| {
22636        Editor::new(
22637            EditorMode::full(),
22638            MultiBuffer::build_from_buffer(buffer, cx),
22639            Some(project.clone()),
22640            window,
22641            cx,
22642        )
22643    });
22644
22645    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22646    let abs_path = project.read_with(cx, |project, cx| {
22647        project
22648            .absolute_path(&project_path, cx)
22649            .map(Arc::from)
22650            .unwrap()
22651    });
22652
22653    // assert we can add breakpoint on the first line
22654    editor.update_in(cx, |editor, window, cx| {
22655        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22656        editor.move_to_end(&MoveToEnd, window, cx);
22657        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22658    });
22659
22660    let breakpoints = editor.update(cx, |editor, cx| {
22661        editor
22662            .breakpoint_store()
22663            .as_ref()
22664            .unwrap()
22665            .read(cx)
22666            .all_source_breakpoints(cx)
22667    });
22668
22669    assert_eq!(1, breakpoints.len());
22670    assert_breakpoint(
22671        &breakpoints,
22672        &abs_path,
22673        vec![
22674            (0, Breakpoint::new_standard()),
22675            (3, Breakpoint::new_standard()),
22676        ],
22677    );
22678
22679    editor.update_in(cx, |editor, window, cx| {
22680        editor.move_to_beginning(&MoveToBeginning, window, cx);
22681        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22682    });
22683
22684    let breakpoints = editor.update(cx, |editor, cx| {
22685        editor
22686            .breakpoint_store()
22687            .as_ref()
22688            .unwrap()
22689            .read(cx)
22690            .all_source_breakpoints(cx)
22691    });
22692
22693    assert_eq!(1, breakpoints.len());
22694    assert_breakpoint(
22695        &breakpoints,
22696        &abs_path,
22697        vec![(3, Breakpoint::new_standard())],
22698    );
22699
22700    editor.update_in(cx, |editor, window, cx| {
22701        editor.move_to_end(&MoveToEnd, window, cx);
22702        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22703    });
22704
22705    let breakpoints = editor.update(cx, |editor, cx| {
22706        editor
22707            .breakpoint_store()
22708            .as_ref()
22709            .unwrap()
22710            .read(cx)
22711            .all_source_breakpoints(cx)
22712    });
22713
22714    assert_eq!(0, breakpoints.len());
22715    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22716}
22717
22718#[gpui::test]
22719async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
22720    init_test(cx, |_| {});
22721
22722    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22723
22724    let fs = FakeFs::new(cx.executor());
22725    fs.insert_tree(
22726        path!("/a"),
22727        json!({
22728            "main.rs": sample_text,
22729        }),
22730    )
22731    .await;
22732    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22733    let (workspace, cx) =
22734        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22735
22736    let worktree_id = workspace.update(cx, |workspace, cx| {
22737        workspace.project().update(cx, |project, cx| {
22738            project.worktrees(cx).next().unwrap().read(cx).id()
22739        })
22740    });
22741
22742    let buffer = project
22743        .update(cx, |project, cx| {
22744            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22745        })
22746        .await
22747        .unwrap();
22748
22749    let (editor, cx) = cx.add_window_view(|window, cx| {
22750        Editor::new(
22751            EditorMode::full(),
22752            MultiBuffer::build_from_buffer(buffer, cx),
22753            Some(project.clone()),
22754            window,
22755            cx,
22756        )
22757    });
22758
22759    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22760    let abs_path = project.read_with(cx, |project, cx| {
22761        project
22762            .absolute_path(&project_path, cx)
22763            .map(Arc::from)
22764            .unwrap()
22765    });
22766
22767    editor.update_in(cx, |editor, window, cx| {
22768        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22769    });
22770
22771    let breakpoints = editor.update(cx, |editor, cx| {
22772        editor
22773            .breakpoint_store()
22774            .as_ref()
22775            .unwrap()
22776            .read(cx)
22777            .all_source_breakpoints(cx)
22778    });
22779
22780    assert_breakpoint(
22781        &breakpoints,
22782        &abs_path,
22783        vec![(0, Breakpoint::new_log("hello world"))],
22784    );
22785
22786    // Removing a log message from a log breakpoint should remove it
22787    editor.update_in(cx, |editor, window, cx| {
22788        add_log_breakpoint_at_cursor(editor, "", window, cx);
22789    });
22790
22791    let breakpoints = editor.update(cx, |editor, cx| {
22792        editor
22793            .breakpoint_store()
22794            .as_ref()
22795            .unwrap()
22796            .read(cx)
22797            .all_source_breakpoints(cx)
22798    });
22799
22800    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22801
22802    editor.update_in(cx, |editor, window, cx| {
22803        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22804        editor.move_to_end(&MoveToEnd, window, cx);
22805        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22806        // Not adding a log message to a standard breakpoint shouldn't remove it
22807        add_log_breakpoint_at_cursor(editor, "", window, cx);
22808    });
22809
22810    let breakpoints = editor.update(cx, |editor, cx| {
22811        editor
22812            .breakpoint_store()
22813            .as_ref()
22814            .unwrap()
22815            .read(cx)
22816            .all_source_breakpoints(cx)
22817    });
22818
22819    assert_breakpoint(
22820        &breakpoints,
22821        &abs_path,
22822        vec![
22823            (0, Breakpoint::new_standard()),
22824            (3, Breakpoint::new_standard()),
22825        ],
22826    );
22827
22828    editor.update_in(cx, |editor, window, cx| {
22829        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22830    });
22831
22832    let breakpoints = editor.update(cx, |editor, cx| {
22833        editor
22834            .breakpoint_store()
22835            .as_ref()
22836            .unwrap()
22837            .read(cx)
22838            .all_source_breakpoints(cx)
22839    });
22840
22841    assert_breakpoint(
22842        &breakpoints,
22843        &abs_path,
22844        vec![
22845            (0, Breakpoint::new_standard()),
22846            (3, Breakpoint::new_log("hello world")),
22847        ],
22848    );
22849
22850    editor.update_in(cx, |editor, window, cx| {
22851        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
22852    });
22853
22854    let breakpoints = editor.update(cx, |editor, cx| {
22855        editor
22856            .breakpoint_store()
22857            .as_ref()
22858            .unwrap()
22859            .read(cx)
22860            .all_source_breakpoints(cx)
22861    });
22862
22863    assert_breakpoint(
22864        &breakpoints,
22865        &abs_path,
22866        vec![
22867            (0, Breakpoint::new_standard()),
22868            (3, Breakpoint::new_log("hello Earth!!")),
22869        ],
22870    );
22871}
22872
22873/// This also tests that Editor::breakpoint_at_cursor_head is working properly
22874/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
22875/// or when breakpoints were placed out of order. This tests for a regression too
22876#[gpui::test]
22877async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
22878    init_test(cx, |_| {});
22879
22880    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22881    let fs = FakeFs::new(cx.executor());
22882    fs.insert_tree(
22883        path!("/a"),
22884        json!({
22885            "main.rs": sample_text,
22886        }),
22887    )
22888    .await;
22889    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22890    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22891    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22892
22893    let fs = FakeFs::new(cx.executor());
22894    fs.insert_tree(
22895        path!("/a"),
22896        json!({
22897            "main.rs": sample_text,
22898        }),
22899    )
22900    .await;
22901    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22902    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22903    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22904    let worktree_id = workspace
22905        .update(cx, |workspace, _window, cx| {
22906            workspace.project().update(cx, |project, cx| {
22907                project.worktrees(cx).next().unwrap().read(cx).id()
22908            })
22909        })
22910        .unwrap();
22911
22912    let buffer = project
22913        .update(cx, |project, cx| {
22914            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22915        })
22916        .await
22917        .unwrap();
22918
22919    let (editor, cx) = cx.add_window_view(|window, cx| {
22920        Editor::new(
22921            EditorMode::full(),
22922            MultiBuffer::build_from_buffer(buffer, cx),
22923            Some(project.clone()),
22924            window,
22925            cx,
22926        )
22927    });
22928
22929    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22930    let abs_path = project.read_with(cx, |project, cx| {
22931        project
22932            .absolute_path(&project_path, cx)
22933            .map(Arc::from)
22934            .unwrap()
22935    });
22936
22937    // assert we can add breakpoint on the first line
22938    editor.update_in(cx, |editor, window, cx| {
22939        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22940        editor.move_to_end(&MoveToEnd, window, cx);
22941        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22942        editor.move_up(&MoveUp, window, cx);
22943        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22944    });
22945
22946    let breakpoints = editor.update(cx, |editor, cx| {
22947        editor
22948            .breakpoint_store()
22949            .as_ref()
22950            .unwrap()
22951            .read(cx)
22952            .all_source_breakpoints(cx)
22953    });
22954
22955    assert_eq!(1, breakpoints.len());
22956    assert_breakpoint(
22957        &breakpoints,
22958        &abs_path,
22959        vec![
22960            (0, Breakpoint::new_standard()),
22961            (2, Breakpoint::new_standard()),
22962            (3, Breakpoint::new_standard()),
22963        ],
22964    );
22965
22966    editor.update_in(cx, |editor, window, cx| {
22967        editor.move_to_beginning(&MoveToBeginning, window, cx);
22968        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22969        editor.move_to_end(&MoveToEnd, window, cx);
22970        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22971        // Disabling a breakpoint that doesn't exist should do nothing
22972        editor.move_up(&MoveUp, window, cx);
22973        editor.move_up(&MoveUp, window, cx);
22974        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22975    });
22976
22977    let breakpoints = editor.update(cx, |editor, cx| {
22978        editor
22979            .breakpoint_store()
22980            .as_ref()
22981            .unwrap()
22982            .read(cx)
22983            .all_source_breakpoints(cx)
22984    });
22985
22986    let disable_breakpoint = {
22987        let mut bp = Breakpoint::new_standard();
22988        bp.state = BreakpointState::Disabled;
22989        bp
22990    };
22991
22992    assert_eq!(1, breakpoints.len());
22993    assert_breakpoint(
22994        &breakpoints,
22995        &abs_path,
22996        vec![
22997            (0, disable_breakpoint.clone()),
22998            (2, Breakpoint::new_standard()),
22999            (3, disable_breakpoint.clone()),
23000        ],
23001    );
23002
23003    editor.update_in(cx, |editor, window, cx| {
23004        editor.move_to_beginning(&MoveToBeginning, window, cx);
23005        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23006        editor.move_to_end(&MoveToEnd, window, cx);
23007        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23008        editor.move_up(&MoveUp, window, cx);
23009        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23010    });
23011
23012    let breakpoints = editor.update(cx, |editor, cx| {
23013        editor
23014            .breakpoint_store()
23015            .as_ref()
23016            .unwrap()
23017            .read(cx)
23018            .all_source_breakpoints(cx)
23019    });
23020
23021    assert_eq!(1, breakpoints.len());
23022    assert_breakpoint(
23023        &breakpoints,
23024        &abs_path,
23025        vec![
23026            (0, Breakpoint::new_standard()),
23027            (2, disable_breakpoint),
23028            (3, Breakpoint::new_standard()),
23029        ],
23030    );
23031}
23032
23033#[gpui::test]
23034async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
23035    init_test(cx, |_| {});
23036    let capabilities = lsp::ServerCapabilities {
23037        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
23038            prepare_provider: Some(true),
23039            work_done_progress_options: Default::default(),
23040        })),
23041        ..Default::default()
23042    };
23043    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23044
23045    cx.set_state(indoc! {"
23046        struct Fˇoo {}
23047    "});
23048
23049    cx.update_editor(|editor, _, cx| {
23050        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23051        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23052        editor.highlight_background::<DocumentHighlightRead>(
23053            &[highlight_range],
23054            |theme| theme.colors().editor_document_highlight_read_background,
23055            cx,
23056        );
23057    });
23058
23059    let mut prepare_rename_handler = cx
23060        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
23061            move |_, _, _| async move {
23062                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
23063                    start: lsp::Position {
23064                        line: 0,
23065                        character: 7,
23066                    },
23067                    end: lsp::Position {
23068                        line: 0,
23069                        character: 10,
23070                    },
23071                })))
23072            },
23073        );
23074    let prepare_rename_task = cx
23075        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23076        .expect("Prepare rename was not started");
23077    prepare_rename_handler.next().await.unwrap();
23078    prepare_rename_task.await.expect("Prepare rename failed");
23079
23080    let mut rename_handler =
23081        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23082            let edit = lsp::TextEdit {
23083                range: lsp::Range {
23084                    start: lsp::Position {
23085                        line: 0,
23086                        character: 7,
23087                    },
23088                    end: lsp::Position {
23089                        line: 0,
23090                        character: 10,
23091                    },
23092                },
23093                new_text: "FooRenamed".to_string(),
23094            };
23095            Ok(Some(lsp::WorkspaceEdit::new(
23096                // Specify the same edit twice
23097                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
23098            )))
23099        });
23100    let rename_task = cx
23101        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23102        .expect("Confirm rename was not started");
23103    rename_handler.next().await.unwrap();
23104    rename_task.await.expect("Confirm rename failed");
23105    cx.run_until_parked();
23106
23107    // Despite two edits, only one is actually applied as those are identical
23108    cx.assert_editor_state(indoc! {"
23109        struct FooRenamedˇ {}
23110    "});
23111}
23112
23113#[gpui::test]
23114async fn test_rename_without_prepare(cx: &mut TestAppContext) {
23115    init_test(cx, |_| {});
23116    // These capabilities indicate that the server does not support prepare rename.
23117    let capabilities = lsp::ServerCapabilities {
23118        rename_provider: Some(lsp::OneOf::Left(true)),
23119        ..Default::default()
23120    };
23121    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23122
23123    cx.set_state(indoc! {"
23124        struct Fˇoo {}
23125    "});
23126
23127    cx.update_editor(|editor, _window, cx| {
23128        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23129        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23130        editor.highlight_background::<DocumentHighlightRead>(
23131            &[highlight_range],
23132            |theme| theme.colors().editor_document_highlight_read_background,
23133            cx,
23134        );
23135    });
23136
23137    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23138        .expect("Prepare rename was not started")
23139        .await
23140        .expect("Prepare rename failed");
23141
23142    let mut rename_handler =
23143        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23144            let edit = lsp::TextEdit {
23145                range: lsp::Range {
23146                    start: lsp::Position {
23147                        line: 0,
23148                        character: 7,
23149                    },
23150                    end: lsp::Position {
23151                        line: 0,
23152                        character: 10,
23153                    },
23154                },
23155                new_text: "FooRenamed".to_string(),
23156            };
23157            Ok(Some(lsp::WorkspaceEdit::new(
23158                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23159            )))
23160        });
23161    let rename_task = cx
23162        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23163        .expect("Confirm rename was not started");
23164    rename_handler.next().await.unwrap();
23165    rename_task.await.expect("Confirm rename failed");
23166    cx.run_until_parked();
23167
23168    // Correct range is renamed, as `surrounding_word` is used to find it.
23169    cx.assert_editor_state(indoc! {"
23170        struct FooRenamedˇ {}
23171    "});
23172}
23173
23174#[gpui::test]
23175async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23176    init_test(cx, |_| {});
23177    let mut cx = EditorTestContext::new(cx).await;
23178
23179    let language = Arc::new(
23180        Language::new(
23181            LanguageConfig::default(),
23182            Some(tree_sitter_html::LANGUAGE.into()),
23183        )
23184        .with_brackets_query(
23185            r#"
23186            ("<" @open "/>" @close)
23187            ("</" @open ">" @close)
23188            ("<" @open ">" @close)
23189            ("\"" @open "\"" @close)
23190            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23191        "#,
23192        )
23193        .unwrap(),
23194    );
23195    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23196
23197    cx.set_state(indoc! {"
23198        <span>ˇ</span>
23199    "});
23200    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23201    cx.assert_editor_state(indoc! {"
23202        <span>
23203        ˇ
23204        </span>
23205    "});
23206
23207    cx.set_state(indoc! {"
23208        <span><span></span>ˇ</span>
23209    "});
23210    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23211    cx.assert_editor_state(indoc! {"
23212        <span><span></span>
23213        ˇ</span>
23214    "});
23215
23216    cx.set_state(indoc! {"
23217        <span>ˇ
23218        </span>
23219    "});
23220    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23221    cx.assert_editor_state(indoc! {"
23222        <span>
23223        ˇ
23224        </span>
23225    "});
23226}
23227
23228#[gpui::test(iterations = 10)]
23229async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23230    init_test(cx, |_| {});
23231
23232    let fs = FakeFs::new(cx.executor());
23233    fs.insert_tree(
23234        path!("/dir"),
23235        json!({
23236            "a.ts": "a",
23237        }),
23238    )
23239    .await;
23240
23241    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23242    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23243    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23244
23245    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23246    language_registry.add(Arc::new(Language::new(
23247        LanguageConfig {
23248            name: "TypeScript".into(),
23249            matcher: LanguageMatcher {
23250                path_suffixes: vec!["ts".to_string()],
23251                ..Default::default()
23252            },
23253            ..Default::default()
23254        },
23255        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
23256    )));
23257    let mut fake_language_servers = language_registry.register_fake_lsp(
23258        "TypeScript",
23259        FakeLspAdapter {
23260            capabilities: lsp::ServerCapabilities {
23261                code_lens_provider: Some(lsp::CodeLensOptions {
23262                    resolve_provider: Some(true),
23263                }),
23264                execute_command_provider: Some(lsp::ExecuteCommandOptions {
23265                    commands: vec!["_the/command".to_string()],
23266                    ..lsp::ExecuteCommandOptions::default()
23267                }),
23268                ..lsp::ServerCapabilities::default()
23269            },
23270            ..FakeLspAdapter::default()
23271        },
23272    );
23273
23274    let editor = workspace
23275        .update(cx, |workspace, window, cx| {
23276            workspace.open_abs_path(
23277                PathBuf::from(path!("/dir/a.ts")),
23278                OpenOptions::default(),
23279                window,
23280                cx,
23281            )
23282        })
23283        .unwrap()
23284        .await
23285        .unwrap()
23286        .downcast::<Editor>()
23287        .unwrap();
23288    cx.executor().run_until_parked();
23289
23290    let fake_server = fake_language_servers.next().await.unwrap();
23291
23292    let buffer = editor.update(cx, |editor, cx| {
23293        editor
23294            .buffer()
23295            .read(cx)
23296            .as_singleton()
23297            .expect("have opened a single file by path")
23298    });
23299
23300    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
23301    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
23302    drop(buffer_snapshot);
23303    let actions = cx
23304        .update_window(*workspace, |_, window, cx| {
23305            project.code_actions(&buffer, anchor..anchor, window, cx)
23306        })
23307        .unwrap();
23308
23309    fake_server
23310        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23311            Ok(Some(vec![
23312                lsp::CodeLens {
23313                    range: lsp::Range::default(),
23314                    command: Some(lsp::Command {
23315                        title: "Code lens command".to_owned(),
23316                        command: "_the/command".to_owned(),
23317                        arguments: None,
23318                    }),
23319                    data: None,
23320                },
23321                lsp::CodeLens {
23322                    range: lsp::Range::default(),
23323                    command: Some(lsp::Command {
23324                        title: "Command not in capabilities".to_owned(),
23325                        command: "not in capabilities".to_owned(),
23326                        arguments: None,
23327                    }),
23328                    data: None,
23329                },
23330                lsp::CodeLens {
23331                    range: lsp::Range {
23332                        start: lsp::Position {
23333                            line: 1,
23334                            character: 1,
23335                        },
23336                        end: lsp::Position {
23337                            line: 1,
23338                            character: 1,
23339                        },
23340                    },
23341                    command: Some(lsp::Command {
23342                        title: "Command not in range".to_owned(),
23343                        command: "_the/command".to_owned(),
23344                        arguments: None,
23345                    }),
23346                    data: None,
23347                },
23348            ]))
23349        })
23350        .next()
23351        .await;
23352
23353    let actions = actions.await.unwrap();
23354    assert_eq!(
23355        actions.len(),
23356        1,
23357        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
23358    );
23359    let action = actions[0].clone();
23360    let apply = project.update(cx, |project, cx| {
23361        project.apply_code_action(buffer.clone(), action, true, cx)
23362    });
23363
23364    // Resolving the code action does not populate its edits. In absence of
23365    // edits, we must execute the given command.
23366    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
23367        |mut lens, _| async move {
23368            let lens_command = lens.command.as_mut().expect("should have a command");
23369            assert_eq!(lens_command.title, "Code lens command");
23370            lens_command.arguments = Some(vec![json!("the-argument")]);
23371            Ok(lens)
23372        },
23373    );
23374
23375    // While executing the command, the language server sends the editor
23376    // a `workspaceEdit` request.
23377    fake_server
23378        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
23379            let fake = fake_server.clone();
23380            move |params, _| {
23381                assert_eq!(params.command, "_the/command");
23382                let fake = fake.clone();
23383                async move {
23384                    fake.server
23385                        .request::<lsp::request::ApplyWorkspaceEdit>(
23386                            lsp::ApplyWorkspaceEditParams {
23387                                label: None,
23388                                edit: lsp::WorkspaceEdit {
23389                                    changes: Some(
23390                                        [(
23391                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
23392                                            vec![lsp::TextEdit {
23393                                                range: lsp::Range::new(
23394                                                    lsp::Position::new(0, 0),
23395                                                    lsp::Position::new(0, 0),
23396                                                ),
23397                                                new_text: "X".into(),
23398                                            }],
23399                                        )]
23400                                        .into_iter()
23401                                        .collect(),
23402                                    ),
23403                                    ..lsp::WorkspaceEdit::default()
23404                                },
23405                            },
23406                        )
23407                        .await
23408                        .into_response()
23409                        .unwrap();
23410                    Ok(Some(json!(null)))
23411                }
23412            }
23413        })
23414        .next()
23415        .await;
23416
23417    // Applying the code lens command returns a project transaction containing the edits
23418    // sent by the language server in its `workspaceEdit` request.
23419    let transaction = apply.await.unwrap();
23420    assert!(transaction.0.contains_key(&buffer));
23421    buffer.update(cx, |buffer, cx| {
23422        assert_eq!(buffer.text(), "Xa");
23423        buffer.undo(cx);
23424        assert_eq!(buffer.text(), "a");
23425    });
23426
23427    let actions_after_edits = cx
23428        .update_window(*workspace, |_, window, cx| {
23429            project.code_actions(&buffer, anchor..anchor, window, cx)
23430        })
23431        .unwrap()
23432        .await
23433        .unwrap();
23434    assert_eq!(
23435        actions, actions_after_edits,
23436        "For the same selection, same code lens actions should be returned"
23437    );
23438
23439    let _responses =
23440        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23441            panic!("No more code lens requests are expected");
23442        });
23443    editor.update_in(cx, |editor, window, cx| {
23444        editor.select_all(&SelectAll, window, cx);
23445    });
23446    cx.executor().run_until_parked();
23447    let new_actions = cx
23448        .update_window(*workspace, |_, window, cx| {
23449            project.code_actions(&buffer, anchor..anchor, window, cx)
23450        })
23451        .unwrap()
23452        .await
23453        .unwrap();
23454    assert_eq!(
23455        actions, new_actions,
23456        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
23457    );
23458}
23459
23460#[gpui::test]
23461async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
23462    init_test(cx, |_| {});
23463
23464    let fs = FakeFs::new(cx.executor());
23465    let main_text = r#"fn main() {
23466println!("1");
23467println!("2");
23468println!("3");
23469println!("4");
23470println!("5");
23471}"#;
23472    let lib_text = "mod foo {}";
23473    fs.insert_tree(
23474        path!("/a"),
23475        json!({
23476            "lib.rs": lib_text,
23477            "main.rs": main_text,
23478        }),
23479    )
23480    .await;
23481
23482    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23483    let (workspace, cx) =
23484        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23485    let worktree_id = workspace.update(cx, |workspace, cx| {
23486        workspace.project().update(cx, |project, cx| {
23487            project.worktrees(cx).next().unwrap().read(cx).id()
23488        })
23489    });
23490
23491    let expected_ranges = vec![
23492        Point::new(0, 0)..Point::new(0, 0),
23493        Point::new(1, 0)..Point::new(1, 1),
23494        Point::new(2, 0)..Point::new(2, 2),
23495        Point::new(3, 0)..Point::new(3, 3),
23496    ];
23497
23498    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23499    let editor_1 = workspace
23500        .update_in(cx, |workspace, window, cx| {
23501            workspace.open_path(
23502                (worktree_id, rel_path("main.rs")),
23503                Some(pane_1.downgrade()),
23504                true,
23505                window,
23506                cx,
23507            )
23508        })
23509        .unwrap()
23510        .await
23511        .downcast::<Editor>()
23512        .unwrap();
23513    pane_1.update(cx, |pane, cx| {
23514        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23515        open_editor.update(cx, |editor, cx| {
23516            assert_eq!(
23517                editor.display_text(cx),
23518                main_text,
23519                "Original main.rs text on initial open",
23520            );
23521            assert_eq!(
23522                editor
23523                    .selections
23524                    .all::<Point>(cx)
23525                    .into_iter()
23526                    .map(|s| s.range())
23527                    .collect::<Vec<_>>(),
23528                vec![Point::zero()..Point::zero()],
23529                "Default selections on initial open",
23530            );
23531        })
23532    });
23533    editor_1.update_in(cx, |editor, window, cx| {
23534        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23535            s.select_ranges(expected_ranges.clone());
23536        });
23537    });
23538
23539    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
23540        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
23541    });
23542    let editor_2 = workspace
23543        .update_in(cx, |workspace, window, cx| {
23544            workspace.open_path(
23545                (worktree_id, rel_path("main.rs")),
23546                Some(pane_2.downgrade()),
23547                true,
23548                window,
23549                cx,
23550            )
23551        })
23552        .unwrap()
23553        .await
23554        .downcast::<Editor>()
23555        .unwrap();
23556    pane_2.update(cx, |pane, cx| {
23557        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23558        open_editor.update(cx, |editor, cx| {
23559            assert_eq!(
23560                editor.display_text(cx),
23561                main_text,
23562                "Original main.rs text on initial open in another panel",
23563            );
23564            assert_eq!(
23565                editor
23566                    .selections
23567                    .all::<Point>(cx)
23568                    .into_iter()
23569                    .map(|s| s.range())
23570                    .collect::<Vec<_>>(),
23571                vec![Point::zero()..Point::zero()],
23572                "Default selections on initial open in another panel",
23573            );
23574        })
23575    });
23576
23577    editor_2.update_in(cx, |editor, window, cx| {
23578        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23579    });
23580
23581    let _other_editor_1 = workspace
23582        .update_in(cx, |workspace, window, cx| {
23583            workspace.open_path(
23584                (worktree_id, rel_path("lib.rs")),
23585                Some(pane_1.downgrade()),
23586                true,
23587                window,
23588                cx,
23589            )
23590        })
23591        .unwrap()
23592        .await
23593        .downcast::<Editor>()
23594        .unwrap();
23595    pane_1
23596        .update_in(cx, |pane, window, cx| {
23597            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23598        })
23599        .await
23600        .unwrap();
23601    drop(editor_1);
23602    pane_1.update(cx, |pane, cx| {
23603        pane.active_item()
23604            .unwrap()
23605            .downcast::<Editor>()
23606            .unwrap()
23607            .update(cx, |editor, cx| {
23608                assert_eq!(
23609                    editor.display_text(cx),
23610                    lib_text,
23611                    "Other file should be open and active",
23612                );
23613            });
23614        assert_eq!(pane.items().count(), 1, "No other editors should be open");
23615    });
23616
23617    let _other_editor_2 = workspace
23618        .update_in(cx, |workspace, window, cx| {
23619            workspace.open_path(
23620                (worktree_id, rel_path("lib.rs")),
23621                Some(pane_2.downgrade()),
23622                true,
23623                window,
23624                cx,
23625            )
23626        })
23627        .unwrap()
23628        .await
23629        .downcast::<Editor>()
23630        .unwrap();
23631    pane_2
23632        .update_in(cx, |pane, window, cx| {
23633            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23634        })
23635        .await
23636        .unwrap();
23637    drop(editor_2);
23638    pane_2.update(cx, |pane, cx| {
23639        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23640        open_editor.update(cx, |editor, cx| {
23641            assert_eq!(
23642                editor.display_text(cx),
23643                lib_text,
23644                "Other file should be open and active in another panel too",
23645            );
23646        });
23647        assert_eq!(
23648            pane.items().count(),
23649            1,
23650            "No other editors should be open in another pane",
23651        );
23652    });
23653
23654    let _editor_1_reopened = workspace
23655        .update_in(cx, |workspace, window, cx| {
23656            workspace.open_path(
23657                (worktree_id, rel_path("main.rs")),
23658                Some(pane_1.downgrade()),
23659                true,
23660                window,
23661                cx,
23662            )
23663        })
23664        .unwrap()
23665        .await
23666        .downcast::<Editor>()
23667        .unwrap();
23668    let _editor_2_reopened = workspace
23669        .update_in(cx, |workspace, window, cx| {
23670            workspace.open_path(
23671                (worktree_id, rel_path("main.rs")),
23672                Some(pane_2.downgrade()),
23673                true,
23674                window,
23675                cx,
23676            )
23677        })
23678        .unwrap()
23679        .await
23680        .downcast::<Editor>()
23681        .unwrap();
23682    pane_1.update(cx, |pane, cx| {
23683        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23684        open_editor.update(cx, |editor, cx| {
23685            assert_eq!(
23686                editor.display_text(cx),
23687                main_text,
23688                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
23689            );
23690            assert_eq!(
23691                editor
23692                    .selections
23693                    .all::<Point>(cx)
23694                    .into_iter()
23695                    .map(|s| s.range())
23696                    .collect::<Vec<_>>(),
23697                expected_ranges,
23698                "Previous editor in the 1st panel had selections and should get them restored on reopen",
23699            );
23700        })
23701    });
23702    pane_2.update(cx, |pane, cx| {
23703        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23704        open_editor.update(cx, |editor, cx| {
23705            assert_eq!(
23706                editor.display_text(cx),
23707                r#"fn main() {
23708⋯rintln!("1");
23709⋯intln!("2");
23710⋯ntln!("3");
23711println!("4");
23712println!("5");
23713}"#,
23714                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
23715            );
23716            assert_eq!(
23717                editor
23718                    .selections
23719                    .all::<Point>(cx)
23720                    .into_iter()
23721                    .map(|s| s.range())
23722                    .collect::<Vec<_>>(),
23723                vec![Point::zero()..Point::zero()],
23724                "Previous editor in the 2nd pane had no selections changed hence should restore none",
23725            );
23726        })
23727    });
23728}
23729
23730#[gpui::test]
23731async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
23732    init_test(cx, |_| {});
23733
23734    let fs = FakeFs::new(cx.executor());
23735    let main_text = r#"fn main() {
23736println!("1");
23737println!("2");
23738println!("3");
23739println!("4");
23740println!("5");
23741}"#;
23742    let lib_text = "mod foo {}";
23743    fs.insert_tree(
23744        path!("/a"),
23745        json!({
23746            "lib.rs": lib_text,
23747            "main.rs": main_text,
23748        }),
23749    )
23750    .await;
23751
23752    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23753    let (workspace, cx) =
23754        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23755    let worktree_id = workspace.update(cx, |workspace, cx| {
23756        workspace.project().update(cx, |project, cx| {
23757            project.worktrees(cx).next().unwrap().read(cx).id()
23758        })
23759    });
23760
23761    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23762    let editor = workspace
23763        .update_in(cx, |workspace, window, cx| {
23764            workspace.open_path(
23765                (worktree_id, rel_path("main.rs")),
23766                Some(pane.downgrade()),
23767                true,
23768                window,
23769                cx,
23770            )
23771        })
23772        .unwrap()
23773        .await
23774        .downcast::<Editor>()
23775        .unwrap();
23776    pane.update(cx, |pane, cx| {
23777        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23778        open_editor.update(cx, |editor, cx| {
23779            assert_eq!(
23780                editor.display_text(cx),
23781                main_text,
23782                "Original main.rs text on initial open",
23783            );
23784        })
23785    });
23786    editor.update_in(cx, |editor, window, cx| {
23787        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
23788    });
23789
23790    cx.update_global(|store: &mut SettingsStore, cx| {
23791        store.update_user_settings(cx, |s| {
23792            s.workspace.restore_on_file_reopen = Some(false);
23793        });
23794    });
23795    editor.update_in(cx, |editor, window, cx| {
23796        editor.fold_ranges(
23797            vec![
23798                Point::new(1, 0)..Point::new(1, 1),
23799                Point::new(2, 0)..Point::new(2, 2),
23800                Point::new(3, 0)..Point::new(3, 3),
23801            ],
23802            false,
23803            window,
23804            cx,
23805        );
23806    });
23807    pane.update_in(cx, |pane, window, cx| {
23808        pane.close_all_items(&CloseAllItems::default(), window, cx)
23809    })
23810    .await
23811    .unwrap();
23812    pane.update(cx, |pane, _| {
23813        assert!(pane.active_item().is_none());
23814    });
23815    cx.update_global(|store: &mut SettingsStore, cx| {
23816        store.update_user_settings(cx, |s| {
23817            s.workspace.restore_on_file_reopen = Some(true);
23818        });
23819    });
23820
23821    let _editor_reopened = workspace
23822        .update_in(cx, |workspace, window, cx| {
23823            workspace.open_path(
23824                (worktree_id, rel_path("main.rs")),
23825                Some(pane.downgrade()),
23826                true,
23827                window,
23828                cx,
23829            )
23830        })
23831        .unwrap()
23832        .await
23833        .downcast::<Editor>()
23834        .unwrap();
23835    pane.update(cx, |pane, cx| {
23836        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23837        open_editor.update(cx, |editor, cx| {
23838            assert_eq!(
23839                editor.display_text(cx),
23840                main_text,
23841                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
23842            );
23843        })
23844    });
23845}
23846
23847#[gpui::test]
23848async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
23849    struct EmptyModalView {
23850        focus_handle: gpui::FocusHandle,
23851    }
23852    impl EventEmitter<DismissEvent> for EmptyModalView {}
23853    impl Render for EmptyModalView {
23854        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
23855            div()
23856        }
23857    }
23858    impl Focusable for EmptyModalView {
23859        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
23860            self.focus_handle.clone()
23861        }
23862    }
23863    impl workspace::ModalView for EmptyModalView {}
23864    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
23865        EmptyModalView {
23866            focus_handle: cx.focus_handle(),
23867        }
23868    }
23869
23870    init_test(cx, |_| {});
23871
23872    let fs = FakeFs::new(cx.executor());
23873    let project = Project::test(fs, [], cx).await;
23874    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23875    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
23876    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23877    let editor = cx.new_window_entity(|window, cx| {
23878        Editor::new(
23879            EditorMode::full(),
23880            buffer,
23881            Some(project.clone()),
23882            window,
23883            cx,
23884        )
23885    });
23886    workspace
23887        .update(cx, |workspace, window, cx| {
23888            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
23889        })
23890        .unwrap();
23891    editor.update_in(cx, |editor, window, cx| {
23892        editor.open_context_menu(&OpenContextMenu, window, cx);
23893        assert!(editor.mouse_context_menu.is_some());
23894    });
23895    workspace
23896        .update(cx, |workspace, window, cx| {
23897            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
23898        })
23899        .unwrap();
23900    cx.read(|cx| {
23901        assert!(editor.read(cx).mouse_context_menu.is_none());
23902    });
23903}
23904
23905fn set_linked_edit_ranges(
23906    opening: (Point, Point),
23907    closing: (Point, Point),
23908    editor: &mut Editor,
23909    cx: &mut Context<Editor>,
23910) {
23911    let Some((buffer, _)) = editor
23912        .buffer
23913        .read(cx)
23914        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
23915    else {
23916        panic!("Failed to get buffer for selection position");
23917    };
23918    let buffer = buffer.read(cx);
23919    let buffer_id = buffer.remote_id();
23920    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
23921    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
23922    let mut linked_ranges = HashMap::default();
23923    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
23924    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
23925}
23926
23927#[gpui::test]
23928async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
23929    init_test(cx, |_| {});
23930
23931    let fs = FakeFs::new(cx.executor());
23932    fs.insert_file(path!("/file.html"), Default::default())
23933        .await;
23934
23935    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
23936
23937    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23938    let html_language = Arc::new(Language::new(
23939        LanguageConfig {
23940            name: "HTML".into(),
23941            matcher: LanguageMatcher {
23942                path_suffixes: vec!["html".to_string()],
23943                ..LanguageMatcher::default()
23944            },
23945            brackets: BracketPairConfig {
23946                pairs: vec![BracketPair {
23947                    start: "<".into(),
23948                    end: ">".into(),
23949                    close: true,
23950                    ..Default::default()
23951                }],
23952                ..Default::default()
23953            },
23954            ..Default::default()
23955        },
23956        Some(tree_sitter_html::LANGUAGE.into()),
23957    ));
23958    language_registry.add(html_language);
23959    let mut fake_servers = language_registry.register_fake_lsp(
23960        "HTML",
23961        FakeLspAdapter {
23962            capabilities: lsp::ServerCapabilities {
23963                completion_provider: Some(lsp::CompletionOptions {
23964                    resolve_provider: Some(true),
23965                    ..Default::default()
23966                }),
23967                ..Default::default()
23968            },
23969            ..Default::default()
23970        },
23971    );
23972
23973    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23974    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23975
23976    let worktree_id = workspace
23977        .update(cx, |workspace, _window, cx| {
23978            workspace.project().update(cx, |project, cx| {
23979                project.worktrees(cx).next().unwrap().read(cx).id()
23980            })
23981        })
23982        .unwrap();
23983    project
23984        .update(cx, |project, cx| {
23985            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
23986        })
23987        .await
23988        .unwrap();
23989    let editor = workspace
23990        .update(cx, |workspace, window, cx| {
23991            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
23992        })
23993        .unwrap()
23994        .await
23995        .unwrap()
23996        .downcast::<Editor>()
23997        .unwrap();
23998
23999    let fake_server = fake_servers.next().await.unwrap();
24000    editor.update_in(cx, |editor, window, cx| {
24001        editor.set_text("<ad></ad>", window, cx);
24002        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24003            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
24004        });
24005        set_linked_edit_ranges(
24006            (Point::new(0, 1), Point::new(0, 3)),
24007            (Point::new(0, 6), Point::new(0, 8)),
24008            editor,
24009            cx,
24010        );
24011    });
24012    let mut completion_handle =
24013        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
24014            Ok(Some(lsp::CompletionResponse::Array(vec![
24015                lsp::CompletionItem {
24016                    label: "head".to_string(),
24017                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24018                        lsp::InsertReplaceEdit {
24019                            new_text: "head".to_string(),
24020                            insert: lsp::Range::new(
24021                                lsp::Position::new(0, 1),
24022                                lsp::Position::new(0, 3),
24023                            ),
24024                            replace: lsp::Range::new(
24025                                lsp::Position::new(0, 1),
24026                                lsp::Position::new(0, 3),
24027                            ),
24028                        },
24029                    )),
24030                    ..Default::default()
24031                },
24032            ])))
24033        });
24034    editor.update_in(cx, |editor, window, cx| {
24035        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
24036    });
24037    cx.run_until_parked();
24038    completion_handle.next().await.unwrap();
24039    editor.update(cx, |editor, _| {
24040        assert!(
24041            editor.context_menu_visible(),
24042            "Completion menu should be visible"
24043        );
24044    });
24045    editor.update_in(cx, |editor, window, cx| {
24046        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
24047    });
24048    cx.executor().run_until_parked();
24049    editor.update(cx, |editor, cx| {
24050        assert_eq!(editor.text(cx), "<head></head>");
24051    });
24052}
24053
24054#[gpui::test]
24055async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
24056    init_test(cx, |_| {});
24057
24058    let mut cx = EditorTestContext::new(cx).await;
24059    let language = Arc::new(Language::new(
24060        LanguageConfig {
24061            name: "TSX".into(),
24062            matcher: LanguageMatcher {
24063                path_suffixes: vec!["tsx".to_string()],
24064                ..LanguageMatcher::default()
24065            },
24066            brackets: BracketPairConfig {
24067                pairs: vec![BracketPair {
24068                    start: "<".into(),
24069                    end: ">".into(),
24070                    close: true,
24071                    ..Default::default()
24072                }],
24073                ..Default::default()
24074            },
24075            linked_edit_characters: HashSet::from_iter(['.']),
24076            ..Default::default()
24077        },
24078        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
24079    ));
24080    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24081
24082    // Test typing > does not extend linked pair
24083    cx.set_state("<divˇ<div></div>");
24084    cx.update_editor(|editor, _, cx| {
24085        set_linked_edit_ranges(
24086            (Point::new(0, 1), Point::new(0, 4)),
24087            (Point::new(0, 11), Point::new(0, 14)),
24088            editor,
24089            cx,
24090        );
24091    });
24092    cx.update_editor(|editor, window, cx| {
24093        editor.handle_input(">", window, cx);
24094    });
24095    cx.assert_editor_state("<div>ˇ<div></div>");
24096
24097    // Test typing . do extend linked pair
24098    cx.set_state("<Animatedˇ></Animated>");
24099    cx.update_editor(|editor, _, cx| {
24100        set_linked_edit_ranges(
24101            (Point::new(0, 1), Point::new(0, 9)),
24102            (Point::new(0, 12), Point::new(0, 20)),
24103            editor,
24104            cx,
24105        );
24106    });
24107    cx.update_editor(|editor, window, cx| {
24108        editor.handle_input(".", window, cx);
24109    });
24110    cx.assert_editor_state("<Animated.ˇ></Animated.>");
24111    cx.update_editor(|editor, _, cx| {
24112        set_linked_edit_ranges(
24113            (Point::new(0, 1), Point::new(0, 10)),
24114            (Point::new(0, 13), Point::new(0, 21)),
24115            editor,
24116            cx,
24117        );
24118    });
24119    cx.update_editor(|editor, window, cx| {
24120        editor.handle_input("V", window, cx);
24121    });
24122    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
24123}
24124
24125#[gpui::test]
24126async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
24127    init_test(cx, |_| {});
24128
24129    let fs = FakeFs::new(cx.executor());
24130    fs.insert_tree(
24131        path!("/root"),
24132        json!({
24133            "a": {
24134                "main.rs": "fn main() {}",
24135            },
24136            "foo": {
24137                "bar": {
24138                    "external_file.rs": "pub mod external {}",
24139                }
24140            }
24141        }),
24142    )
24143    .await;
24144
24145    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
24146    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24147    language_registry.add(rust_lang());
24148    let _fake_servers = language_registry.register_fake_lsp(
24149        "Rust",
24150        FakeLspAdapter {
24151            ..FakeLspAdapter::default()
24152        },
24153    );
24154    let (workspace, cx) =
24155        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24156    let worktree_id = workspace.update(cx, |workspace, cx| {
24157        workspace.project().update(cx, |project, cx| {
24158            project.worktrees(cx).next().unwrap().read(cx).id()
24159        })
24160    });
24161
24162    let assert_language_servers_count =
24163        |expected: usize, context: &str, cx: &mut VisualTestContext| {
24164            project.update(cx, |project, cx| {
24165                let current = project
24166                    .lsp_store()
24167                    .read(cx)
24168                    .as_local()
24169                    .unwrap()
24170                    .language_servers
24171                    .len();
24172                assert_eq!(expected, current, "{context}");
24173            });
24174        };
24175
24176    assert_language_servers_count(
24177        0,
24178        "No servers should be running before any file is open",
24179        cx,
24180    );
24181    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24182    let main_editor = workspace
24183        .update_in(cx, |workspace, window, cx| {
24184            workspace.open_path(
24185                (worktree_id, rel_path("main.rs")),
24186                Some(pane.downgrade()),
24187                true,
24188                window,
24189                cx,
24190            )
24191        })
24192        .unwrap()
24193        .await
24194        .downcast::<Editor>()
24195        .unwrap();
24196    pane.update(cx, |pane, cx| {
24197        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24198        open_editor.update(cx, |editor, cx| {
24199            assert_eq!(
24200                editor.display_text(cx),
24201                "fn main() {}",
24202                "Original main.rs text on initial open",
24203            );
24204        });
24205        assert_eq!(open_editor, main_editor);
24206    });
24207    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24208
24209    let external_editor = workspace
24210        .update_in(cx, |workspace, window, cx| {
24211            workspace.open_abs_path(
24212                PathBuf::from("/root/foo/bar/external_file.rs"),
24213                OpenOptions::default(),
24214                window,
24215                cx,
24216            )
24217        })
24218        .await
24219        .expect("opening external file")
24220        .downcast::<Editor>()
24221        .expect("downcasted external file's open element to editor");
24222    pane.update(cx, |pane, cx| {
24223        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24224        open_editor.update(cx, |editor, cx| {
24225            assert_eq!(
24226                editor.display_text(cx),
24227                "pub mod external {}",
24228                "External file is open now",
24229            );
24230        });
24231        assert_eq!(open_editor, external_editor);
24232    });
24233    assert_language_servers_count(
24234        1,
24235        "Second, external, *.rs file should join the existing server",
24236        cx,
24237    );
24238
24239    pane.update_in(cx, |pane, window, cx| {
24240        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24241    })
24242    .await
24243    .unwrap();
24244    pane.update_in(cx, |pane, window, cx| {
24245        pane.navigate_backward(&Default::default(), window, cx);
24246    });
24247    cx.run_until_parked();
24248    pane.update(cx, |pane, cx| {
24249        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24250        open_editor.update(cx, |editor, cx| {
24251            assert_eq!(
24252                editor.display_text(cx),
24253                "pub mod external {}",
24254                "External file is open now",
24255            );
24256        });
24257    });
24258    assert_language_servers_count(
24259        1,
24260        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
24261        cx,
24262    );
24263
24264    cx.update(|_, cx| {
24265        workspace::reload(cx);
24266    });
24267    assert_language_servers_count(
24268        1,
24269        "After reloading the worktree with local and external files opened, only one project should be started",
24270        cx,
24271    );
24272}
24273
24274#[gpui::test]
24275async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
24276    init_test(cx, |_| {});
24277
24278    let mut cx = EditorTestContext::new(cx).await;
24279    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24280    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24281
24282    // test cursor move to start of each line on tab
24283    // for `if`, `elif`, `else`, `while`, `with` and `for`
24284    cx.set_state(indoc! {"
24285        def main():
24286        ˇ    for item in items:
24287        ˇ        while item.active:
24288        ˇ            if item.value > 10:
24289        ˇ                continue
24290        ˇ            elif item.value < 0:
24291        ˇ                break
24292        ˇ            else:
24293        ˇ                with item.context() as ctx:
24294        ˇ                    yield count
24295        ˇ        else:
24296        ˇ            log('while else')
24297        ˇ    else:
24298        ˇ        log('for else')
24299    "});
24300    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24301    cx.assert_editor_state(indoc! {"
24302        def main():
24303            ˇfor item in items:
24304                ˇwhile item.active:
24305                    ˇif item.value > 10:
24306                        ˇcontinue
24307                    ˇelif item.value < 0:
24308                        ˇbreak
24309                    ˇelse:
24310                        ˇwith item.context() as ctx:
24311                            ˇyield count
24312                ˇelse:
24313                    ˇlog('while else')
24314            ˇelse:
24315                ˇlog('for else')
24316    "});
24317    // test relative indent is preserved when tab
24318    // for `if`, `elif`, `else`, `while`, `with` and `for`
24319    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24320    cx.assert_editor_state(indoc! {"
24321        def main():
24322                ˇfor item in items:
24323                    ˇwhile item.active:
24324                        ˇif item.value > 10:
24325                            ˇcontinue
24326                        ˇelif item.value < 0:
24327                            ˇbreak
24328                        ˇelse:
24329                            ˇwith item.context() as ctx:
24330                                ˇyield count
24331                    ˇelse:
24332                        ˇlog('while else')
24333                ˇelse:
24334                    ˇlog('for else')
24335    "});
24336
24337    // test cursor move to start of each line on tab
24338    // for `try`, `except`, `else`, `finally`, `match` and `def`
24339    cx.set_state(indoc! {"
24340        def main():
24341        ˇ    try:
24342        ˇ        fetch()
24343        ˇ    except ValueError:
24344        ˇ        handle_error()
24345        ˇ    else:
24346        ˇ        match value:
24347        ˇ            case _:
24348        ˇ    finally:
24349        ˇ        def status():
24350        ˇ            return 0
24351    "});
24352    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24353    cx.assert_editor_state(indoc! {"
24354        def main():
24355            ˇtry:
24356                ˇfetch()
24357            ˇexcept ValueError:
24358                ˇhandle_error()
24359            ˇelse:
24360                ˇmatch value:
24361                    ˇcase _:
24362            ˇfinally:
24363                ˇdef status():
24364                    ˇreturn 0
24365    "});
24366    // test relative indent is preserved when tab
24367    // for `try`, `except`, `else`, `finally`, `match` and `def`
24368    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24369    cx.assert_editor_state(indoc! {"
24370        def main():
24371                ˇtry:
24372                    ˇfetch()
24373                ˇexcept ValueError:
24374                    ˇhandle_error()
24375                ˇelse:
24376                    ˇmatch value:
24377                        ˇcase _:
24378                ˇfinally:
24379                    ˇdef status():
24380                        ˇreturn 0
24381    "});
24382}
24383
24384#[gpui::test]
24385async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
24386    init_test(cx, |_| {});
24387
24388    let mut cx = EditorTestContext::new(cx).await;
24389    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24390    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24391
24392    // test `else` auto outdents when typed inside `if` block
24393    cx.set_state(indoc! {"
24394        def main():
24395            if i == 2:
24396                return
24397                ˇ
24398    "});
24399    cx.update_editor(|editor, window, cx| {
24400        editor.handle_input("else:", window, cx);
24401    });
24402    cx.assert_editor_state(indoc! {"
24403        def main():
24404            if i == 2:
24405                return
24406            else:ˇ
24407    "});
24408
24409    // test `except` auto outdents when typed inside `try` block
24410    cx.set_state(indoc! {"
24411        def main():
24412            try:
24413                i = 2
24414                ˇ
24415    "});
24416    cx.update_editor(|editor, window, cx| {
24417        editor.handle_input("except:", window, cx);
24418    });
24419    cx.assert_editor_state(indoc! {"
24420        def main():
24421            try:
24422                i = 2
24423            except:ˇ
24424    "});
24425
24426    // test `else` auto outdents when typed inside `except` block
24427    cx.set_state(indoc! {"
24428        def main():
24429            try:
24430                i = 2
24431            except:
24432                j = 2
24433                ˇ
24434    "});
24435    cx.update_editor(|editor, window, cx| {
24436        editor.handle_input("else:", window, cx);
24437    });
24438    cx.assert_editor_state(indoc! {"
24439        def main():
24440            try:
24441                i = 2
24442            except:
24443                j = 2
24444            else:ˇ
24445    "});
24446
24447    // test `finally` auto outdents when typed inside `else` block
24448    cx.set_state(indoc! {"
24449        def main():
24450            try:
24451                i = 2
24452            except:
24453                j = 2
24454            else:
24455                k = 2
24456                ˇ
24457    "});
24458    cx.update_editor(|editor, window, cx| {
24459        editor.handle_input("finally:", window, cx);
24460    });
24461    cx.assert_editor_state(indoc! {"
24462        def main():
24463            try:
24464                i = 2
24465            except:
24466                j = 2
24467            else:
24468                k = 2
24469            finally:ˇ
24470    "});
24471
24472    // test `else` does not outdents when typed inside `except` block right after for block
24473    cx.set_state(indoc! {"
24474        def main():
24475            try:
24476                i = 2
24477            except:
24478                for i in range(n):
24479                    pass
24480                ˇ
24481    "});
24482    cx.update_editor(|editor, window, cx| {
24483        editor.handle_input("else:", window, cx);
24484    });
24485    cx.assert_editor_state(indoc! {"
24486        def main():
24487            try:
24488                i = 2
24489            except:
24490                for i in range(n):
24491                    pass
24492                else:ˇ
24493    "});
24494
24495    // test `finally` auto outdents when typed inside `else` block right after for block
24496    cx.set_state(indoc! {"
24497        def main():
24498            try:
24499                i = 2
24500            except:
24501                j = 2
24502            else:
24503                for i in range(n):
24504                    pass
24505                ˇ
24506    "});
24507    cx.update_editor(|editor, window, cx| {
24508        editor.handle_input("finally:", window, cx);
24509    });
24510    cx.assert_editor_state(indoc! {"
24511        def main():
24512            try:
24513                i = 2
24514            except:
24515                j = 2
24516            else:
24517                for i in range(n):
24518                    pass
24519            finally:ˇ
24520    "});
24521
24522    // test `except` outdents to inner "try" block
24523    cx.set_state(indoc! {"
24524        def main():
24525            try:
24526                i = 2
24527                if i == 2:
24528                    try:
24529                        i = 3
24530                        ˇ
24531    "});
24532    cx.update_editor(|editor, window, cx| {
24533        editor.handle_input("except:", window, cx);
24534    });
24535    cx.assert_editor_state(indoc! {"
24536        def main():
24537            try:
24538                i = 2
24539                if i == 2:
24540                    try:
24541                        i = 3
24542                    except:ˇ
24543    "});
24544
24545    // test `except` outdents to outer "try" block
24546    cx.set_state(indoc! {"
24547        def main():
24548            try:
24549                i = 2
24550                if i == 2:
24551                    try:
24552                        i = 3
24553                ˇ
24554    "});
24555    cx.update_editor(|editor, window, cx| {
24556        editor.handle_input("except:", window, cx);
24557    });
24558    cx.assert_editor_state(indoc! {"
24559        def main():
24560            try:
24561                i = 2
24562                if i == 2:
24563                    try:
24564                        i = 3
24565            except:ˇ
24566    "});
24567
24568    // test `else` stays at correct indent when typed after `for` block
24569    cx.set_state(indoc! {"
24570        def main():
24571            for i in range(10):
24572                if i == 3:
24573                    break
24574            ˇ
24575    "});
24576    cx.update_editor(|editor, window, cx| {
24577        editor.handle_input("else:", window, cx);
24578    });
24579    cx.assert_editor_state(indoc! {"
24580        def main():
24581            for i in range(10):
24582                if i == 3:
24583                    break
24584            else:ˇ
24585    "});
24586
24587    // test does not outdent on typing after line with square brackets
24588    cx.set_state(indoc! {"
24589        def f() -> list[str]:
24590            ˇ
24591    "});
24592    cx.update_editor(|editor, window, cx| {
24593        editor.handle_input("a", window, cx);
24594    });
24595    cx.assert_editor_state(indoc! {"
24596        def f() -> list[str]:
2459724598    "});
24599
24600    // test does not outdent on typing : after case keyword
24601    cx.set_state(indoc! {"
24602        match 1:
24603            caseˇ
24604    "});
24605    cx.update_editor(|editor, window, cx| {
24606        editor.handle_input(":", window, cx);
24607    });
24608    cx.assert_editor_state(indoc! {"
24609        match 1:
24610            case:ˇ
24611    "});
24612}
24613
24614#[gpui::test]
24615async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
24616    init_test(cx, |_| {});
24617    update_test_language_settings(cx, |settings| {
24618        settings.defaults.extend_comment_on_newline = Some(false);
24619    });
24620    let mut cx = EditorTestContext::new(cx).await;
24621    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24622    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24623
24624    // test correct indent after newline on comment
24625    cx.set_state(indoc! {"
24626        # COMMENT:ˇ
24627    "});
24628    cx.update_editor(|editor, window, cx| {
24629        editor.newline(&Newline, window, cx);
24630    });
24631    cx.assert_editor_state(indoc! {"
24632        # COMMENT:
24633        ˇ
24634    "});
24635
24636    // test correct indent after newline in brackets
24637    cx.set_state(indoc! {"
24638        {ˇ}
24639    "});
24640    cx.update_editor(|editor, window, cx| {
24641        editor.newline(&Newline, window, cx);
24642    });
24643    cx.run_until_parked();
24644    cx.assert_editor_state(indoc! {"
24645        {
24646            ˇ
24647        }
24648    "});
24649
24650    cx.set_state(indoc! {"
24651        (ˇ)
24652    "});
24653    cx.update_editor(|editor, window, cx| {
24654        editor.newline(&Newline, window, cx);
24655    });
24656    cx.run_until_parked();
24657    cx.assert_editor_state(indoc! {"
24658        (
24659            ˇ
24660        )
24661    "});
24662
24663    // do not indent after empty lists or dictionaries
24664    cx.set_state(indoc! {"
24665        a = []ˇ
24666    "});
24667    cx.update_editor(|editor, window, cx| {
24668        editor.newline(&Newline, window, cx);
24669    });
24670    cx.run_until_parked();
24671    cx.assert_editor_state(indoc! {"
24672        a = []
24673        ˇ
24674    "});
24675}
24676
24677#[gpui::test]
24678async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
24679    init_test(cx, |_| {});
24680
24681    let mut cx = EditorTestContext::new(cx).await;
24682    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24683    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24684
24685    // test cursor move to start of each line on tab
24686    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
24687    cx.set_state(indoc! {"
24688        function main() {
24689        ˇ    for item in $items; do
24690        ˇ        while [ -n \"$item\" ]; do
24691        ˇ            if [ \"$value\" -gt 10 ]; then
24692        ˇ                continue
24693        ˇ            elif [ \"$value\" -lt 0 ]; then
24694        ˇ                break
24695        ˇ            else
24696        ˇ                echo \"$item\"
24697        ˇ            fi
24698        ˇ        done
24699        ˇ    done
24700        ˇ}
24701    "});
24702    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24703    cx.assert_editor_state(indoc! {"
24704        function main() {
24705            ˇfor item in $items; do
24706                ˇwhile [ -n \"$item\" ]; do
24707                    ˇif [ \"$value\" -gt 10 ]; then
24708                        ˇcontinue
24709                    ˇelif [ \"$value\" -lt 0 ]; then
24710                        ˇbreak
24711                    ˇelse
24712                        ˇecho \"$item\"
24713                    ˇfi
24714                ˇdone
24715            ˇdone
24716        ˇ}
24717    "});
24718    // test relative indent is preserved when tab
24719    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24720    cx.assert_editor_state(indoc! {"
24721        function main() {
24722                ˇfor item in $items; do
24723                    ˇwhile [ -n \"$item\" ]; do
24724                        ˇif [ \"$value\" -gt 10 ]; then
24725                            ˇcontinue
24726                        ˇelif [ \"$value\" -lt 0 ]; then
24727                            ˇbreak
24728                        ˇelse
24729                            ˇecho \"$item\"
24730                        ˇfi
24731                    ˇdone
24732                ˇdone
24733            ˇ}
24734    "});
24735
24736    // test cursor move to start of each line on tab
24737    // for `case` statement with patterns
24738    cx.set_state(indoc! {"
24739        function handle() {
24740        ˇ    case \"$1\" in
24741        ˇ        start)
24742        ˇ            echo \"a\"
24743        ˇ            ;;
24744        ˇ        stop)
24745        ˇ            echo \"b\"
24746        ˇ            ;;
24747        ˇ        *)
24748        ˇ            echo \"c\"
24749        ˇ            ;;
24750        ˇ    esac
24751        ˇ}
24752    "});
24753    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24754    cx.assert_editor_state(indoc! {"
24755        function handle() {
24756            ˇcase \"$1\" in
24757                ˇstart)
24758                    ˇecho \"a\"
24759                    ˇ;;
24760                ˇstop)
24761                    ˇecho \"b\"
24762                    ˇ;;
24763                ˇ*)
24764                    ˇecho \"c\"
24765                    ˇ;;
24766            ˇesac
24767        ˇ}
24768    "});
24769}
24770
24771#[gpui::test]
24772async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
24773    init_test(cx, |_| {});
24774
24775    let mut cx = EditorTestContext::new(cx).await;
24776    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24777    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24778
24779    // test indents on comment insert
24780    cx.set_state(indoc! {"
24781        function main() {
24782        ˇ    for item in $items; do
24783        ˇ        while [ -n \"$item\" ]; do
24784        ˇ            if [ \"$value\" -gt 10 ]; then
24785        ˇ                continue
24786        ˇ            elif [ \"$value\" -lt 0 ]; then
24787        ˇ                break
24788        ˇ            else
24789        ˇ                echo \"$item\"
24790        ˇ            fi
24791        ˇ        done
24792        ˇ    done
24793        ˇ}
24794    "});
24795    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
24796    cx.assert_editor_state(indoc! {"
24797        function main() {
24798        #ˇ    for item in $items; do
24799        #ˇ        while [ -n \"$item\" ]; do
24800        #ˇ            if [ \"$value\" -gt 10 ]; then
24801        #ˇ                continue
24802        #ˇ            elif [ \"$value\" -lt 0 ]; then
24803        #ˇ                break
24804        #ˇ            else
24805        #ˇ                echo \"$item\"
24806        #ˇ            fi
24807        #ˇ        done
24808        #ˇ    done
24809        #ˇ}
24810    "});
24811}
24812
24813#[gpui::test]
24814async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
24815    init_test(cx, |_| {});
24816
24817    let mut cx = EditorTestContext::new(cx).await;
24818    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24819    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24820
24821    // test `else` auto outdents when typed inside `if` block
24822    cx.set_state(indoc! {"
24823        if [ \"$1\" = \"test\" ]; then
24824            echo \"foo bar\"
24825            ˇ
24826    "});
24827    cx.update_editor(|editor, window, cx| {
24828        editor.handle_input("else", window, cx);
24829    });
24830    cx.assert_editor_state(indoc! {"
24831        if [ \"$1\" = \"test\" ]; then
24832            echo \"foo bar\"
24833        elseˇ
24834    "});
24835
24836    // test `elif` auto outdents when typed inside `if` block
24837    cx.set_state(indoc! {"
24838        if [ \"$1\" = \"test\" ]; then
24839            echo \"foo bar\"
24840            ˇ
24841    "});
24842    cx.update_editor(|editor, window, cx| {
24843        editor.handle_input("elif", window, cx);
24844    });
24845    cx.assert_editor_state(indoc! {"
24846        if [ \"$1\" = \"test\" ]; then
24847            echo \"foo bar\"
24848        elifˇ
24849    "});
24850
24851    // test `fi` auto outdents when typed inside `else` block
24852    cx.set_state(indoc! {"
24853        if [ \"$1\" = \"test\" ]; then
24854            echo \"foo bar\"
24855        else
24856            echo \"bar baz\"
24857            ˇ
24858    "});
24859    cx.update_editor(|editor, window, cx| {
24860        editor.handle_input("fi", window, cx);
24861    });
24862    cx.assert_editor_state(indoc! {"
24863        if [ \"$1\" = \"test\" ]; then
24864            echo \"foo bar\"
24865        else
24866            echo \"bar baz\"
24867        fiˇ
24868    "});
24869
24870    // test `done` auto outdents when typed inside `while` block
24871    cx.set_state(indoc! {"
24872        while read line; do
24873            echo \"$line\"
24874            ˇ
24875    "});
24876    cx.update_editor(|editor, window, cx| {
24877        editor.handle_input("done", window, cx);
24878    });
24879    cx.assert_editor_state(indoc! {"
24880        while read line; do
24881            echo \"$line\"
24882        doneˇ
24883    "});
24884
24885    // test `done` auto outdents when typed inside `for` block
24886    cx.set_state(indoc! {"
24887        for file in *.txt; do
24888            cat \"$file\"
24889            ˇ
24890    "});
24891    cx.update_editor(|editor, window, cx| {
24892        editor.handle_input("done", window, cx);
24893    });
24894    cx.assert_editor_state(indoc! {"
24895        for file in *.txt; do
24896            cat \"$file\"
24897        doneˇ
24898    "});
24899
24900    // test `esac` auto outdents when typed inside `case` block
24901    cx.set_state(indoc! {"
24902        case \"$1\" in
24903            start)
24904                echo \"foo bar\"
24905                ;;
24906            stop)
24907                echo \"bar baz\"
24908                ;;
24909            ˇ
24910    "});
24911    cx.update_editor(|editor, window, cx| {
24912        editor.handle_input("esac", window, cx);
24913    });
24914    cx.assert_editor_state(indoc! {"
24915        case \"$1\" in
24916            start)
24917                echo \"foo bar\"
24918                ;;
24919            stop)
24920                echo \"bar baz\"
24921                ;;
24922        esacˇ
24923    "});
24924
24925    // test `*)` auto outdents when typed inside `case` block
24926    cx.set_state(indoc! {"
24927        case \"$1\" in
24928            start)
24929                echo \"foo bar\"
24930                ;;
24931                ˇ
24932    "});
24933    cx.update_editor(|editor, window, cx| {
24934        editor.handle_input("*)", window, cx);
24935    });
24936    cx.assert_editor_state(indoc! {"
24937        case \"$1\" in
24938            start)
24939                echo \"foo bar\"
24940                ;;
24941            *)ˇ
24942    "});
24943
24944    // test `fi` outdents to correct level with nested if blocks
24945    cx.set_state(indoc! {"
24946        if [ \"$1\" = \"test\" ]; then
24947            echo \"outer if\"
24948            if [ \"$2\" = \"debug\" ]; then
24949                echo \"inner if\"
24950                ˇ
24951    "});
24952    cx.update_editor(|editor, window, cx| {
24953        editor.handle_input("fi", window, cx);
24954    });
24955    cx.assert_editor_state(indoc! {"
24956        if [ \"$1\" = \"test\" ]; then
24957            echo \"outer if\"
24958            if [ \"$2\" = \"debug\" ]; then
24959                echo \"inner if\"
24960            fiˇ
24961    "});
24962}
24963
24964#[gpui::test]
24965async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
24966    init_test(cx, |_| {});
24967    update_test_language_settings(cx, |settings| {
24968        settings.defaults.extend_comment_on_newline = Some(false);
24969    });
24970    let mut cx = EditorTestContext::new(cx).await;
24971    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24972    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24973
24974    // test correct indent after newline on comment
24975    cx.set_state(indoc! {"
24976        # COMMENT:ˇ
24977    "});
24978    cx.update_editor(|editor, window, cx| {
24979        editor.newline(&Newline, window, cx);
24980    });
24981    cx.assert_editor_state(indoc! {"
24982        # COMMENT:
24983        ˇ
24984    "});
24985
24986    // test correct indent after newline after `then`
24987    cx.set_state(indoc! {"
24988
24989        if [ \"$1\" = \"test\" ]; thenˇ
24990    "});
24991    cx.update_editor(|editor, window, cx| {
24992        editor.newline(&Newline, window, cx);
24993    });
24994    cx.run_until_parked();
24995    cx.assert_editor_state(indoc! {"
24996
24997        if [ \"$1\" = \"test\" ]; then
24998            ˇ
24999    "});
25000
25001    // test correct indent after newline after `else`
25002    cx.set_state(indoc! {"
25003        if [ \"$1\" = \"test\" ]; then
25004        elseˇ
25005    "});
25006    cx.update_editor(|editor, window, cx| {
25007        editor.newline(&Newline, window, cx);
25008    });
25009    cx.run_until_parked();
25010    cx.assert_editor_state(indoc! {"
25011        if [ \"$1\" = \"test\" ]; then
25012        else
25013            ˇ
25014    "});
25015
25016    // test correct indent after newline after `elif`
25017    cx.set_state(indoc! {"
25018        if [ \"$1\" = \"test\" ]; then
25019        elifˇ
25020    "});
25021    cx.update_editor(|editor, window, cx| {
25022        editor.newline(&Newline, window, cx);
25023    });
25024    cx.run_until_parked();
25025    cx.assert_editor_state(indoc! {"
25026        if [ \"$1\" = \"test\" ]; then
25027        elif
25028            ˇ
25029    "});
25030
25031    // test correct indent after newline after `do`
25032    cx.set_state(indoc! {"
25033        for file in *.txt; doˇ
25034    "});
25035    cx.update_editor(|editor, window, cx| {
25036        editor.newline(&Newline, window, cx);
25037    });
25038    cx.run_until_parked();
25039    cx.assert_editor_state(indoc! {"
25040        for file in *.txt; do
25041            ˇ
25042    "});
25043
25044    // test correct indent after newline after case pattern
25045    cx.set_state(indoc! {"
25046        case \"$1\" in
25047            start)ˇ
25048    "});
25049    cx.update_editor(|editor, window, cx| {
25050        editor.newline(&Newline, window, cx);
25051    });
25052    cx.run_until_parked();
25053    cx.assert_editor_state(indoc! {"
25054        case \"$1\" in
25055            start)
25056                ˇ
25057    "});
25058
25059    // test correct indent after newline after case pattern
25060    cx.set_state(indoc! {"
25061        case \"$1\" in
25062            start)
25063                ;;
25064            *)ˇ
25065    "});
25066    cx.update_editor(|editor, window, cx| {
25067        editor.newline(&Newline, window, cx);
25068    });
25069    cx.run_until_parked();
25070    cx.assert_editor_state(indoc! {"
25071        case \"$1\" in
25072            start)
25073                ;;
25074            *)
25075                ˇ
25076    "});
25077
25078    // test correct indent after newline after function opening brace
25079    cx.set_state(indoc! {"
25080        function test() {ˇ}
25081    "});
25082    cx.update_editor(|editor, window, cx| {
25083        editor.newline(&Newline, window, cx);
25084    });
25085    cx.run_until_parked();
25086    cx.assert_editor_state(indoc! {"
25087        function test() {
25088            ˇ
25089        }
25090    "});
25091
25092    // test no extra indent after semicolon on same line
25093    cx.set_state(indoc! {"
25094        echo \"test\"25095    "});
25096    cx.update_editor(|editor, window, cx| {
25097        editor.newline(&Newline, window, cx);
25098    });
25099    cx.run_until_parked();
25100    cx.assert_editor_state(indoc! {"
25101        echo \"test\";
25102        ˇ
25103    "});
25104}
25105
25106fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
25107    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
25108    point..point
25109}
25110
25111#[track_caller]
25112fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
25113    let (text, ranges) = marked_text_ranges(marked_text, true);
25114    assert_eq!(editor.text(cx), text);
25115    assert_eq!(
25116        editor.selections.ranges(cx),
25117        ranges,
25118        "Assert selections are {}",
25119        marked_text
25120    );
25121}
25122
25123pub fn handle_signature_help_request(
25124    cx: &mut EditorLspTestContext,
25125    mocked_response: lsp::SignatureHelp,
25126) -> impl Future<Output = ()> + use<> {
25127    let mut request =
25128        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
25129            let mocked_response = mocked_response.clone();
25130            async move { Ok(Some(mocked_response)) }
25131        });
25132
25133    async move {
25134        request.next().await;
25135    }
25136}
25137
25138#[track_caller]
25139pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
25140    cx.update_editor(|editor, _, _| {
25141        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
25142            let entries = menu.entries.borrow();
25143            let entries = entries
25144                .iter()
25145                .map(|entry| entry.string.as_str())
25146                .collect::<Vec<_>>();
25147            assert_eq!(entries, expected);
25148        } else {
25149            panic!("Expected completions menu");
25150        }
25151    });
25152}
25153
25154/// Handle completion request passing a marked string specifying where the completion
25155/// should be triggered from using '|' character, what range should be replaced, and what completions
25156/// should be returned using '<' and '>' to delimit the range.
25157///
25158/// Also see `handle_completion_request_with_insert_and_replace`.
25159#[track_caller]
25160pub fn handle_completion_request(
25161    marked_string: &str,
25162    completions: Vec<&'static str>,
25163    is_incomplete: bool,
25164    counter: Arc<AtomicUsize>,
25165    cx: &mut EditorLspTestContext,
25166) -> impl Future<Output = ()> {
25167    let complete_from_marker: TextRangeMarker = '|'.into();
25168    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25169    let (_, mut marked_ranges) = marked_text_ranges_by(
25170        marked_string,
25171        vec![complete_from_marker.clone(), replace_range_marker.clone()],
25172    );
25173
25174    let complete_from_position =
25175        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25176    let replace_range =
25177        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25178
25179    let mut request =
25180        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25181            let completions = completions.clone();
25182            counter.fetch_add(1, atomic::Ordering::Release);
25183            async move {
25184                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25185                assert_eq!(
25186                    params.text_document_position.position,
25187                    complete_from_position
25188                );
25189                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
25190                    is_incomplete,
25191                    item_defaults: None,
25192                    items: completions
25193                        .iter()
25194                        .map(|completion_text| lsp::CompletionItem {
25195                            label: completion_text.to_string(),
25196                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
25197                                range: replace_range,
25198                                new_text: completion_text.to_string(),
25199                            })),
25200                            ..Default::default()
25201                        })
25202                        .collect(),
25203                })))
25204            }
25205        });
25206
25207    async move {
25208        request.next().await;
25209    }
25210}
25211
25212/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
25213/// given instead, which also contains an `insert` range.
25214///
25215/// This function uses markers to define ranges:
25216/// - `|` marks the cursor position
25217/// - `<>` marks the replace range
25218/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
25219pub fn handle_completion_request_with_insert_and_replace(
25220    cx: &mut EditorLspTestContext,
25221    marked_string: &str,
25222    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
25223    counter: Arc<AtomicUsize>,
25224) -> impl Future<Output = ()> {
25225    let complete_from_marker: TextRangeMarker = '|'.into();
25226    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25227    let insert_range_marker: TextRangeMarker = ('{', '}').into();
25228
25229    let (_, mut marked_ranges) = marked_text_ranges_by(
25230        marked_string,
25231        vec![
25232            complete_from_marker.clone(),
25233            replace_range_marker.clone(),
25234            insert_range_marker.clone(),
25235        ],
25236    );
25237
25238    let complete_from_position =
25239        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25240    let replace_range =
25241        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25242
25243    let insert_range = match marked_ranges.remove(&insert_range_marker) {
25244        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
25245        _ => lsp::Range {
25246            start: replace_range.start,
25247            end: complete_from_position,
25248        },
25249    };
25250
25251    let mut request =
25252        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25253            let completions = completions.clone();
25254            counter.fetch_add(1, atomic::Ordering::Release);
25255            async move {
25256                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25257                assert_eq!(
25258                    params.text_document_position.position, complete_from_position,
25259                    "marker `|` position doesn't match",
25260                );
25261                Ok(Some(lsp::CompletionResponse::Array(
25262                    completions
25263                        .iter()
25264                        .map(|(label, new_text)| lsp::CompletionItem {
25265                            label: label.to_string(),
25266                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25267                                lsp::InsertReplaceEdit {
25268                                    insert: insert_range,
25269                                    replace: replace_range,
25270                                    new_text: new_text.to_string(),
25271                                },
25272                            )),
25273                            ..Default::default()
25274                        })
25275                        .collect(),
25276                )))
25277            }
25278        });
25279
25280    async move {
25281        request.next().await;
25282    }
25283}
25284
25285fn handle_resolve_completion_request(
25286    cx: &mut EditorLspTestContext,
25287    edits: Option<Vec<(&'static str, &'static str)>>,
25288) -> impl Future<Output = ()> {
25289    let edits = edits.map(|edits| {
25290        edits
25291            .iter()
25292            .map(|(marked_string, new_text)| {
25293                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
25294                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
25295                lsp::TextEdit::new(replace_range, new_text.to_string())
25296            })
25297            .collect::<Vec<_>>()
25298    });
25299
25300    let mut request =
25301        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
25302            let edits = edits.clone();
25303            async move {
25304                Ok(lsp::CompletionItem {
25305                    additional_text_edits: edits,
25306                    ..Default::default()
25307                })
25308            }
25309        });
25310
25311    async move {
25312        request.next().await;
25313    }
25314}
25315
25316pub(crate) fn update_test_language_settings(
25317    cx: &mut TestAppContext,
25318    f: impl Fn(&mut AllLanguageSettingsContent),
25319) {
25320    cx.update(|cx| {
25321        SettingsStore::update_global(cx, |store, cx| {
25322            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
25323        });
25324    });
25325}
25326
25327pub(crate) fn update_test_project_settings(
25328    cx: &mut TestAppContext,
25329    f: impl Fn(&mut ProjectSettingsContent),
25330) {
25331    cx.update(|cx| {
25332        SettingsStore::update_global(cx, |store, cx| {
25333            store.update_user_settings(cx, |settings| f(&mut settings.project));
25334        });
25335    });
25336}
25337
25338pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
25339    cx.update(|cx| {
25340        assets::Assets.load_test_fonts(cx);
25341        let store = SettingsStore::test(cx);
25342        cx.set_global(store);
25343        theme::init(theme::LoadThemes::JustBase, cx);
25344        release_channel::init(SemanticVersion::default(), cx);
25345        client::init_settings(cx);
25346        language::init(cx);
25347        Project::init_settings(cx);
25348        workspace::init_settings(cx);
25349        crate::init(cx);
25350    });
25351    zlog::init_test();
25352    update_test_language_settings(cx, f);
25353}
25354
25355#[track_caller]
25356fn assert_hunk_revert(
25357    not_reverted_text_with_selections: &str,
25358    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
25359    expected_reverted_text_with_selections: &str,
25360    base_text: &str,
25361    cx: &mut EditorLspTestContext,
25362) {
25363    cx.set_state(not_reverted_text_with_selections);
25364    cx.set_head_text(base_text);
25365    cx.executor().run_until_parked();
25366
25367    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
25368        let snapshot = editor.snapshot(window, cx);
25369        let reverted_hunk_statuses = snapshot
25370            .buffer_snapshot()
25371            .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
25372            .map(|hunk| hunk.status().kind)
25373            .collect::<Vec<_>>();
25374
25375        editor.git_restore(&Default::default(), window, cx);
25376        reverted_hunk_statuses
25377    });
25378    cx.executor().run_until_parked();
25379    cx.assert_editor_state(expected_reverted_text_with_selections);
25380    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
25381}
25382
25383#[gpui::test(iterations = 10)]
25384async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
25385    init_test(cx, |_| {});
25386
25387    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
25388    let counter = diagnostic_requests.clone();
25389
25390    let fs = FakeFs::new(cx.executor());
25391    fs.insert_tree(
25392        path!("/a"),
25393        json!({
25394            "first.rs": "fn main() { let a = 5; }",
25395            "second.rs": "// Test file",
25396        }),
25397    )
25398    .await;
25399
25400    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25401    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25402    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25403
25404    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25405    language_registry.add(rust_lang());
25406    let mut fake_servers = language_registry.register_fake_lsp(
25407        "Rust",
25408        FakeLspAdapter {
25409            capabilities: lsp::ServerCapabilities {
25410                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
25411                    lsp::DiagnosticOptions {
25412                        identifier: None,
25413                        inter_file_dependencies: true,
25414                        workspace_diagnostics: true,
25415                        work_done_progress_options: Default::default(),
25416                    },
25417                )),
25418                ..Default::default()
25419            },
25420            ..Default::default()
25421        },
25422    );
25423
25424    let editor = workspace
25425        .update(cx, |workspace, window, cx| {
25426            workspace.open_abs_path(
25427                PathBuf::from(path!("/a/first.rs")),
25428                OpenOptions::default(),
25429                window,
25430                cx,
25431            )
25432        })
25433        .unwrap()
25434        .await
25435        .unwrap()
25436        .downcast::<Editor>()
25437        .unwrap();
25438    let fake_server = fake_servers.next().await.unwrap();
25439    let server_id = fake_server.server.server_id();
25440    let mut first_request = fake_server
25441        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
25442            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
25443            let result_id = Some(new_result_id.to_string());
25444            assert_eq!(
25445                params.text_document.uri,
25446                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25447            );
25448            async move {
25449                Ok(lsp::DocumentDiagnosticReportResult::Report(
25450                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
25451                        related_documents: None,
25452                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
25453                            items: Vec::new(),
25454                            result_id,
25455                        },
25456                    }),
25457                ))
25458            }
25459        });
25460
25461    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
25462        project.update(cx, |project, cx| {
25463            let buffer_id = editor
25464                .read(cx)
25465                .buffer()
25466                .read(cx)
25467                .as_singleton()
25468                .expect("created a singleton buffer")
25469                .read(cx)
25470                .remote_id();
25471            let buffer_result_id = project
25472                .lsp_store()
25473                .read(cx)
25474                .result_id(server_id, buffer_id, cx);
25475            assert_eq!(expected, buffer_result_id);
25476        });
25477    };
25478
25479    ensure_result_id(None, cx);
25480    cx.executor().advance_clock(Duration::from_millis(60));
25481    cx.executor().run_until_parked();
25482    assert_eq!(
25483        diagnostic_requests.load(atomic::Ordering::Acquire),
25484        1,
25485        "Opening file should trigger diagnostic request"
25486    );
25487    first_request
25488        .next()
25489        .await
25490        .expect("should have sent the first diagnostics pull request");
25491    ensure_result_id(Some("1".to_string()), cx);
25492
25493    // Editing should trigger diagnostics
25494    editor.update_in(cx, |editor, window, cx| {
25495        editor.handle_input("2", window, cx)
25496    });
25497    cx.executor().advance_clock(Duration::from_millis(60));
25498    cx.executor().run_until_parked();
25499    assert_eq!(
25500        diagnostic_requests.load(atomic::Ordering::Acquire),
25501        2,
25502        "Editing should trigger diagnostic request"
25503    );
25504    ensure_result_id(Some("2".to_string()), cx);
25505
25506    // Moving cursor should not trigger diagnostic request
25507    editor.update_in(cx, |editor, window, cx| {
25508        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25509            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
25510        });
25511    });
25512    cx.executor().advance_clock(Duration::from_millis(60));
25513    cx.executor().run_until_parked();
25514    assert_eq!(
25515        diagnostic_requests.load(atomic::Ordering::Acquire),
25516        2,
25517        "Cursor movement should not trigger diagnostic request"
25518    );
25519    ensure_result_id(Some("2".to_string()), cx);
25520    // Multiple rapid edits should be debounced
25521    for _ in 0..5 {
25522        editor.update_in(cx, |editor, window, cx| {
25523            editor.handle_input("x", window, cx)
25524        });
25525    }
25526    cx.executor().advance_clock(Duration::from_millis(60));
25527    cx.executor().run_until_parked();
25528
25529    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
25530    assert!(
25531        final_requests <= 4,
25532        "Multiple rapid edits should be debounced (got {final_requests} requests)",
25533    );
25534    ensure_result_id(Some(final_requests.to_string()), cx);
25535}
25536
25537#[gpui::test]
25538async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
25539    // Regression test for issue #11671
25540    // Previously, adding a cursor after moving multiple cursors would reset
25541    // the cursor count instead of adding to the existing cursors.
25542    init_test(cx, |_| {});
25543    let mut cx = EditorTestContext::new(cx).await;
25544
25545    // Create a simple buffer with cursor at start
25546    cx.set_state(indoc! {"
25547        ˇaaaa
25548        bbbb
25549        cccc
25550        dddd
25551        eeee
25552        ffff
25553        gggg
25554        hhhh"});
25555
25556    // Add 2 cursors below (so we have 3 total)
25557    cx.update_editor(|editor, window, cx| {
25558        editor.add_selection_below(&Default::default(), window, cx);
25559        editor.add_selection_below(&Default::default(), window, cx);
25560    });
25561
25562    // Verify we have 3 cursors
25563    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
25564    assert_eq!(
25565        initial_count, 3,
25566        "Should have 3 cursors after adding 2 below"
25567    );
25568
25569    // Move down one line
25570    cx.update_editor(|editor, window, cx| {
25571        editor.move_down(&MoveDown, window, cx);
25572    });
25573
25574    // Add another cursor below
25575    cx.update_editor(|editor, window, cx| {
25576        editor.add_selection_below(&Default::default(), window, cx);
25577    });
25578
25579    // Should now have 4 cursors (3 original + 1 new)
25580    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
25581    assert_eq!(
25582        final_count, 4,
25583        "Should have 4 cursors after moving and adding another"
25584    );
25585}
25586
25587#[gpui::test(iterations = 10)]
25588async fn test_document_colors(cx: &mut TestAppContext) {
25589    let expected_color = Rgba {
25590        r: 0.33,
25591        g: 0.33,
25592        b: 0.33,
25593        a: 0.33,
25594    };
25595
25596    init_test(cx, |_| {});
25597
25598    let fs = FakeFs::new(cx.executor());
25599    fs.insert_tree(
25600        path!("/a"),
25601        json!({
25602            "first.rs": "fn main() { let a = 5; }",
25603        }),
25604    )
25605    .await;
25606
25607    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25608    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25609    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25610
25611    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25612    language_registry.add(rust_lang());
25613    let mut fake_servers = language_registry.register_fake_lsp(
25614        "Rust",
25615        FakeLspAdapter {
25616            capabilities: lsp::ServerCapabilities {
25617                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
25618                ..lsp::ServerCapabilities::default()
25619            },
25620            name: "rust-analyzer",
25621            ..FakeLspAdapter::default()
25622        },
25623    );
25624    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
25625        "Rust",
25626        FakeLspAdapter {
25627            capabilities: lsp::ServerCapabilities {
25628                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
25629                ..lsp::ServerCapabilities::default()
25630            },
25631            name: "not-rust-analyzer",
25632            ..FakeLspAdapter::default()
25633        },
25634    );
25635
25636    let editor = workspace
25637        .update(cx, |workspace, window, cx| {
25638            workspace.open_abs_path(
25639                PathBuf::from(path!("/a/first.rs")),
25640                OpenOptions::default(),
25641                window,
25642                cx,
25643            )
25644        })
25645        .unwrap()
25646        .await
25647        .unwrap()
25648        .downcast::<Editor>()
25649        .unwrap();
25650    let fake_language_server = fake_servers.next().await.unwrap();
25651    let fake_language_server_without_capabilities =
25652        fake_servers_without_capabilities.next().await.unwrap();
25653    let requests_made = Arc::new(AtomicUsize::new(0));
25654    let closure_requests_made = Arc::clone(&requests_made);
25655    let mut color_request_handle = fake_language_server
25656        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25657            let requests_made = Arc::clone(&closure_requests_made);
25658            async move {
25659                assert_eq!(
25660                    params.text_document.uri,
25661                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25662                );
25663                requests_made.fetch_add(1, atomic::Ordering::Release);
25664                Ok(vec![
25665                    lsp::ColorInformation {
25666                        range: lsp::Range {
25667                            start: lsp::Position {
25668                                line: 0,
25669                                character: 0,
25670                            },
25671                            end: lsp::Position {
25672                                line: 0,
25673                                character: 1,
25674                            },
25675                        },
25676                        color: lsp::Color {
25677                            red: 0.33,
25678                            green: 0.33,
25679                            blue: 0.33,
25680                            alpha: 0.33,
25681                        },
25682                    },
25683                    lsp::ColorInformation {
25684                        range: lsp::Range {
25685                            start: lsp::Position {
25686                                line: 0,
25687                                character: 0,
25688                            },
25689                            end: lsp::Position {
25690                                line: 0,
25691                                character: 1,
25692                            },
25693                        },
25694                        color: lsp::Color {
25695                            red: 0.33,
25696                            green: 0.33,
25697                            blue: 0.33,
25698                            alpha: 0.33,
25699                        },
25700                    },
25701                ])
25702            }
25703        });
25704
25705    let _handle = fake_language_server_without_capabilities
25706        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
25707            panic!("Should not be called");
25708        });
25709    cx.executor().advance_clock(Duration::from_millis(100));
25710    color_request_handle.next().await.unwrap();
25711    cx.run_until_parked();
25712    assert_eq!(
25713        1,
25714        requests_made.load(atomic::Ordering::Acquire),
25715        "Should query for colors once per editor open"
25716    );
25717    editor.update_in(cx, |editor, _, cx| {
25718        assert_eq!(
25719            vec![expected_color],
25720            extract_color_inlays(editor, cx),
25721            "Should have an initial inlay"
25722        );
25723    });
25724
25725    // opening another file in a split should not influence the LSP query counter
25726    workspace
25727        .update(cx, |workspace, window, cx| {
25728            assert_eq!(
25729                workspace.panes().len(),
25730                1,
25731                "Should have one pane with one editor"
25732            );
25733            workspace.move_item_to_pane_in_direction(
25734                &MoveItemToPaneInDirection {
25735                    direction: SplitDirection::Right,
25736                    focus: false,
25737                    clone: true,
25738                },
25739                window,
25740                cx,
25741            );
25742        })
25743        .unwrap();
25744    cx.run_until_parked();
25745    workspace
25746        .update(cx, |workspace, _, cx| {
25747            let panes = workspace.panes();
25748            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
25749            for pane in panes {
25750                let editor = pane
25751                    .read(cx)
25752                    .active_item()
25753                    .and_then(|item| item.downcast::<Editor>())
25754                    .expect("Should have opened an editor in each split");
25755                let editor_file = editor
25756                    .read(cx)
25757                    .buffer()
25758                    .read(cx)
25759                    .as_singleton()
25760                    .expect("test deals with singleton buffers")
25761                    .read(cx)
25762                    .file()
25763                    .expect("test buffese should have a file")
25764                    .path();
25765                assert_eq!(
25766                    editor_file.as_ref(),
25767                    rel_path("first.rs"),
25768                    "Both editors should be opened for the same file"
25769                )
25770            }
25771        })
25772        .unwrap();
25773
25774    cx.executor().advance_clock(Duration::from_millis(500));
25775    let save = editor.update_in(cx, |editor, window, cx| {
25776        editor.move_to_end(&MoveToEnd, window, cx);
25777        editor.handle_input("dirty", window, cx);
25778        editor.save(
25779            SaveOptions {
25780                format: true,
25781                autosave: true,
25782            },
25783            project.clone(),
25784            window,
25785            cx,
25786        )
25787    });
25788    save.await.unwrap();
25789
25790    color_request_handle.next().await.unwrap();
25791    cx.run_until_parked();
25792    assert_eq!(
25793        3,
25794        requests_made.load(atomic::Ordering::Acquire),
25795        "Should query for colors once per save and once per formatting after save"
25796    );
25797
25798    drop(editor);
25799    let close = workspace
25800        .update(cx, |workspace, window, cx| {
25801            workspace.active_pane().update(cx, |pane, cx| {
25802                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25803            })
25804        })
25805        .unwrap();
25806    close.await.unwrap();
25807    let close = workspace
25808        .update(cx, |workspace, window, cx| {
25809            workspace.active_pane().update(cx, |pane, cx| {
25810                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25811            })
25812        })
25813        .unwrap();
25814    close.await.unwrap();
25815    assert_eq!(
25816        3,
25817        requests_made.load(atomic::Ordering::Acquire),
25818        "After saving and closing all editors, no extra requests should be made"
25819    );
25820    workspace
25821        .update(cx, |workspace, _, cx| {
25822            assert!(
25823                workspace.active_item(cx).is_none(),
25824                "Should close all editors"
25825            )
25826        })
25827        .unwrap();
25828
25829    workspace
25830        .update(cx, |workspace, window, cx| {
25831            workspace.active_pane().update(cx, |pane, cx| {
25832                pane.navigate_backward(&workspace::GoBack, window, cx);
25833            })
25834        })
25835        .unwrap();
25836    cx.executor().advance_clock(Duration::from_millis(100));
25837    cx.run_until_parked();
25838    let editor = workspace
25839        .update(cx, |workspace, _, cx| {
25840            workspace
25841                .active_item(cx)
25842                .expect("Should have reopened the editor again after navigating back")
25843                .downcast::<Editor>()
25844                .expect("Should be an editor")
25845        })
25846        .unwrap();
25847    color_request_handle.next().await.unwrap();
25848    assert_eq!(
25849        3,
25850        requests_made.load(atomic::Ordering::Acquire),
25851        "Cache should be reused on buffer close and reopen"
25852    );
25853    editor.update(cx, |editor, cx| {
25854        assert_eq!(
25855            vec![expected_color],
25856            extract_color_inlays(editor, cx),
25857            "Should have an initial inlay"
25858        );
25859    });
25860
25861    drop(color_request_handle);
25862    let closure_requests_made = Arc::clone(&requests_made);
25863    let mut empty_color_request_handle = fake_language_server
25864        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25865            let requests_made = Arc::clone(&closure_requests_made);
25866            async move {
25867                assert_eq!(
25868                    params.text_document.uri,
25869                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25870                );
25871                requests_made.fetch_add(1, atomic::Ordering::Release);
25872                Ok(Vec::new())
25873            }
25874        });
25875    let save = editor.update_in(cx, |editor, window, cx| {
25876        editor.move_to_end(&MoveToEnd, window, cx);
25877        editor.handle_input("dirty_again", window, cx);
25878        editor.save(
25879            SaveOptions {
25880                format: false,
25881                autosave: true,
25882            },
25883            project.clone(),
25884            window,
25885            cx,
25886        )
25887    });
25888    save.await.unwrap();
25889
25890    empty_color_request_handle.next().await.unwrap();
25891    cx.run_until_parked();
25892    assert_eq!(
25893        4,
25894        requests_made.load(atomic::Ordering::Acquire),
25895        "Should query for colors once per save only, as formatting was not requested"
25896    );
25897    editor.update(cx, |editor, cx| {
25898        assert_eq!(
25899            Vec::<Rgba>::new(),
25900            extract_color_inlays(editor, cx),
25901            "Should clear all colors when the server returns an empty response"
25902        );
25903    });
25904}
25905
25906#[gpui::test]
25907async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
25908    init_test(cx, |_| {});
25909    let (editor, cx) = cx.add_window_view(Editor::single_line);
25910    editor.update_in(cx, |editor, window, cx| {
25911        editor.set_text("oops\n\nwow\n", window, cx)
25912    });
25913    cx.run_until_parked();
25914    editor.update(cx, |editor, cx| {
25915        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
25916    });
25917    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
25918    cx.run_until_parked();
25919    editor.update(cx, |editor, cx| {
25920        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
25921    });
25922}
25923
25924#[gpui::test]
25925async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
25926    init_test(cx, |_| {});
25927
25928    cx.update(|cx| {
25929        register_project_item::<Editor>(cx);
25930    });
25931
25932    let fs = FakeFs::new(cx.executor());
25933    fs.insert_tree("/root1", json!({})).await;
25934    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
25935        .await;
25936
25937    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
25938    let (workspace, cx) =
25939        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25940
25941    let worktree_id = project.update(cx, |project, cx| {
25942        project.worktrees(cx).next().unwrap().read(cx).id()
25943    });
25944
25945    let handle = workspace
25946        .update_in(cx, |workspace, window, cx| {
25947            let project_path = (worktree_id, rel_path("one.pdf"));
25948            workspace.open_path(project_path, None, true, window, cx)
25949        })
25950        .await
25951        .unwrap();
25952
25953    assert_eq!(
25954        handle.to_any().entity_type(),
25955        TypeId::of::<InvalidBufferView>()
25956    );
25957}
25958
25959#[gpui::test]
25960async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
25961    init_test(cx, |_| {});
25962
25963    let language = Arc::new(Language::new(
25964        LanguageConfig::default(),
25965        Some(tree_sitter_rust::LANGUAGE.into()),
25966    ));
25967
25968    // Test hierarchical sibling navigation
25969    let text = r#"
25970        fn outer() {
25971            if condition {
25972                let a = 1;
25973            }
25974            let b = 2;
25975        }
25976
25977        fn another() {
25978            let c = 3;
25979        }
25980    "#;
25981
25982    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
25983    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25984    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
25985
25986    // Wait for parsing to complete
25987    editor
25988        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
25989        .await;
25990
25991    editor.update_in(cx, |editor, window, cx| {
25992        // Start by selecting "let a = 1;" inside the if block
25993        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25994            s.select_display_ranges([
25995                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
25996            ]);
25997        });
25998
25999        let initial_selection = editor.selections.display_ranges(cx);
26000        assert_eq!(initial_selection.len(), 1, "Should have one selection");
26001
26002        // Test select next sibling - should move up levels to find the next sibling
26003        // Since "let a = 1;" has no siblings in the if block, it should move up
26004        // to find "let b = 2;" which is a sibling of the if block
26005        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26006        let next_selection = editor.selections.display_ranges(cx);
26007
26008        // Should have a selection and it should be different from the initial
26009        assert_eq!(
26010            next_selection.len(),
26011            1,
26012            "Should have one selection after next"
26013        );
26014        assert_ne!(
26015            next_selection[0], initial_selection[0],
26016            "Next sibling selection should be different"
26017        );
26018
26019        // Test hierarchical navigation by going to the end of the current function
26020        // and trying to navigate to the next function
26021        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26022            s.select_display_ranges([
26023                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
26024            ]);
26025        });
26026
26027        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26028        let function_next_selection = editor.selections.display_ranges(cx);
26029
26030        // Should move to the next function
26031        assert_eq!(
26032            function_next_selection.len(),
26033            1,
26034            "Should have one selection after function next"
26035        );
26036
26037        // Test select previous sibling navigation
26038        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
26039        let prev_selection = editor.selections.display_ranges(cx);
26040
26041        // Should have a selection and it should be different
26042        assert_eq!(
26043            prev_selection.len(),
26044            1,
26045            "Should have one selection after prev"
26046        );
26047        assert_ne!(
26048            prev_selection[0], function_next_selection[0],
26049            "Previous sibling selection should be different from next"
26050        );
26051    });
26052}
26053
26054#[gpui::test]
26055async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
26056    init_test(cx, |_| {});
26057
26058    let mut cx = EditorTestContext::new(cx).await;
26059    cx.set_state(
26060        "let ˇvariable = 42;
26061let another = variable + 1;
26062let result = variable * 2;",
26063    );
26064
26065    // Set up document highlights manually (simulating LSP response)
26066    cx.update_editor(|editor, _window, cx| {
26067        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
26068
26069        // Create highlights for "variable" occurrences
26070        let highlight_ranges = [
26071            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
26072            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
26073            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
26074        ];
26075
26076        let anchor_ranges: Vec<_> = highlight_ranges
26077            .iter()
26078            .map(|range| range.clone().to_anchors(&buffer_snapshot))
26079            .collect();
26080
26081        editor.highlight_background::<DocumentHighlightRead>(
26082            &anchor_ranges,
26083            |theme| theme.colors().editor_document_highlight_read_background,
26084            cx,
26085        );
26086    });
26087
26088    // Go to next highlight - should move to second "variable"
26089    cx.update_editor(|editor, window, cx| {
26090        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26091    });
26092    cx.assert_editor_state(
26093        "let variable = 42;
26094let another = ˇvariable + 1;
26095let result = variable * 2;",
26096    );
26097
26098    // Go to next highlight - should move to third "variable"
26099    cx.update_editor(|editor, window, cx| {
26100        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26101    });
26102    cx.assert_editor_state(
26103        "let variable = 42;
26104let another = variable + 1;
26105let result = ˇvariable * 2;",
26106    );
26107
26108    // Go to next highlight - should stay at third "variable" (no wrap-around)
26109    cx.update_editor(|editor, window, cx| {
26110        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26111    });
26112    cx.assert_editor_state(
26113        "let variable = 42;
26114let another = variable + 1;
26115let result = ˇvariable * 2;",
26116    );
26117
26118    // Now test going backwards from third position
26119    cx.update_editor(|editor, window, cx| {
26120        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26121    });
26122    cx.assert_editor_state(
26123        "let variable = 42;
26124let another = ˇvariable + 1;
26125let result = variable * 2;",
26126    );
26127
26128    // Go to previous highlight - should move to first "variable"
26129    cx.update_editor(|editor, window, cx| {
26130        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26131    });
26132    cx.assert_editor_state(
26133        "let ˇvariable = 42;
26134let another = variable + 1;
26135let result = variable * 2;",
26136    );
26137
26138    // Go to previous highlight - should stay on first "variable"
26139    cx.update_editor(|editor, window, cx| {
26140        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26141    });
26142    cx.assert_editor_state(
26143        "let ˇvariable = 42;
26144let another = variable + 1;
26145let result = variable * 2;",
26146    );
26147}
26148
26149#[gpui::test]
26150async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
26151    cx: &mut gpui::TestAppContext,
26152) {
26153    init_test(cx, |_| {});
26154
26155    let url = "https://zed.dev";
26156
26157    let markdown_language = Arc::new(Language::new(
26158        LanguageConfig {
26159            name: "Markdown".into(),
26160            ..LanguageConfig::default()
26161        },
26162        None,
26163    ));
26164
26165    let mut cx = EditorTestContext::new(cx).await;
26166    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26167    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
26168
26169    cx.update_editor(|editor, window, cx| {
26170        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26171        editor.paste(&Paste, window, cx);
26172    });
26173
26174    cx.assert_editor_state(&format!(
26175        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
26176    ));
26177}
26178
26179#[gpui::test]
26180async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
26181    cx: &mut gpui::TestAppContext,
26182) {
26183    init_test(cx, |_| {});
26184
26185    let url = "https://zed.dev";
26186
26187    let markdown_language = Arc::new(Language::new(
26188        LanguageConfig {
26189            name: "Markdown".into(),
26190            ..LanguageConfig::default()
26191        },
26192        None,
26193    ));
26194
26195    let mut cx = EditorTestContext::new(cx).await;
26196    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26197    cx.set_state(&format!(
26198        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
26199    ));
26200
26201    cx.update_editor(|editor, window, cx| {
26202        editor.copy(&Copy, window, cx);
26203    });
26204
26205    cx.set_state(&format!(
26206        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
26207    ));
26208
26209    cx.update_editor(|editor, window, cx| {
26210        editor.paste(&Paste, window, cx);
26211    });
26212
26213    cx.assert_editor_state(&format!(
26214        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
26215    ));
26216}
26217
26218#[gpui::test]
26219async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
26220    cx: &mut gpui::TestAppContext,
26221) {
26222    init_test(cx, |_| {});
26223
26224    let url = "https://zed.dev";
26225
26226    let markdown_language = Arc::new(Language::new(
26227        LanguageConfig {
26228            name: "Markdown".into(),
26229            ..LanguageConfig::default()
26230        },
26231        None,
26232    ));
26233
26234    let mut cx = EditorTestContext::new(cx).await;
26235    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26236    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
26237
26238    cx.update_editor(|editor, window, cx| {
26239        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26240        editor.paste(&Paste, window, cx);
26241    });
26242
26243    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
26244}
26245
26246#[gpui::test]
26247async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
26248    cx: &mut gpui::TestAppContext,
26249) {
26250    init_test(cx, |_| {});
26251
26252    let text = "Awesome";
26253
26254    let markdown_language = Arc::new(Language::new(
26255        LanguageConfig {
26256            name: "Markdown".into(),
26257            ..LanguageConfig::default()
26258        },
26259        None,
26260    ));
26261
26262    let mut cx = EditorTestContext::new(cx).await;
26263    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26264    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
26265
26266    cx.update_editor(|editor, window, cx| {
26267        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
26268        editor.paste(&Paste, window, cx);
26269    });
26270
26271    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
26272}
26273
26274#[gpui::test]
26275async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
26276    cx: &mut gpui::TestAppContext,
26277) {
26278    init_test(cx, |_| {});
26279
26280    let url = "https://zed.dev";
26281
26282    let markdown_language = Arc::new(Language::new(
26283        LanguageConfig {
26284            name: "Rust".into(),
26285            ..LanguageConfig::default()
26286        },
26287        None,
26288    ));
26289
26290    let mut cx = EditorTestContext::new(cx).await;
26291    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26292    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
26293
26294    cx.update_editor(|editor, window, cx| {
26295        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26296        editor.paste(&Paste, window, cx);
26297    });
26298
26299    cx.assert_editor_state(&format!(
26300        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
26301    ));
26302}
26303
26304#[gpui::test]
26305async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
26306    cx: &mut TestAppContext,
26307) {
26308    init_test(cx, |_| {});
26309
26310    let url = "https://zed.dev";
26311
26312    let markdown_language = Arc::new(Language::new(
26313        LanguageConfig {
26314            name: "Markdown".into(),
26315            ..LanguageConfig::default()
26316        },
26317        None,
26318    ));
26319
26320    let (editor, cx) = cx.add_window_view(|window, cx| {
26321        let multi_buffer = MultiBuffer::build_multi(
26322            [
26323                ("this will embed -> link", vec![Point::row_range(0..1)]),
26324                ("this will replace -> link", vec![Point::row_range(0..1)]),
26325            ],
26326            cx,
26327        );
26328        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
26329        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26330            s.select_ranges(vec![
26331                Point::new(0, 19)..Point::new(0, 23),
26332                Point::new(1, 21)..Point::new(1, 25),
26333            ])
26334        });
26335        let first_buffer_id = multi_buffer
26336            .read(cx)
26337            .excerpt_buffer_ids()
26338            .into_iter()
26339            .next()
26340            .unwrap();
26341        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
26342        first_buffer.update(cx, |buffer, cx| {
26343            buffer.set_language(Some(markdown_language.clone()), cx);
26344        });
26345
26346        editor
26347    });
26348    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
26349
26350    cx.update_editor(|editor, window, cx| {
26351        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26352        editor.paste(&Paste, window, cx);
26353    });
26354
26355    cx.assert_editor_state(&format!(
26356        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
26357    ));
26358}
26359
26360#[gpui::test]
26361async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
26362    init_test(cx, |_| {});
26363
26364    let fs = FakeFs::new(cx.executor());
26365    fs.insert_tree(
26366        path!("/project"),
26367        json!({
26368            "first.rs": "# First Document\nSome content here.",
26369            "second.rs": "Plain text content for second file.",
26370        }),
26371    )
26372    .await;
26373
26374    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
26375    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26376    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26377
26378    let language = rust_lang();
26379    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26380    language_registry.add(language.clone());
26381    let mut fake_servers = language_registry.register_fake_lsp(
26382        "Rust",
26383        FakeLspAdapter {
26384            ..FakeLspAdapter::default()
26385        },
26386    );
26387
26388    let buffer1 = project
26389        .update(cx, |project, cx| {
26390            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
26391        })
26392        .await
26393        .unwrap();
26394    let buffer2 = project
26395        .update(cx, |project, cx| {
26396            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
26397        })
26398        .await
26399        .unwrap();
26400
26401    let multi_buffer = cx.new(|cx| {
26402        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
26403        multi_buffer.set_excerpts_for_path(
26404            PathKey::for_buffer(&buffer1, cx),
26405            buffer1.clone(),
26406            [Point::zero()..buffer1.read(cx).max_point()],
26407            3,
26408            cx,
26409        );
26410        multi_buffer.set_excerpts_for_path(
26411            PathKey::for_buffer(&buffer2, cx),
26412            buffer2.clone(),
26413            [Point::zero()..buffer1.read(cx).max_point()],
26414            3,
26415            cx,
26416        );
26417        multi_buffer
26418    });
26419
26420    let (editor, cx) = cx.add_window_view(|window, cx| {
26421        Editor::new(
26422            EditorMode::full(),
26423            multi_buffer,
26424            Some(project.clone()),
26425            window,
26426            cx,
26427        )
26428    });
26429
26430    let fake_language_server = fake_servers.next().await.unwrap();
26431
26432    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
26433
26434    let save = editor.update_in(cx, |editor, window, cx| {
26435        assert!(editor.is_dirty(cx));
26436
26437        editor.save(
26438            SaveOptions {
26439                format: true,
26440                autosave: true,
26441            },
26442            project,
26443            window,
26444            cx,
26445        )
26446    });
26447    let (start_edit_tx, start_edit_rx) = oneshot::channel();
26448    let (done_edit_tx, done_edit_rx) = oneshot::channel();
26449    let mut done_edit_rx = Some(done_edit_rx);
26450    let mut start_edit_tx = Some(start_edit_tx);
26451
26452    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
26453        start_edit_tx.take().unwrap().send(()).unwrap();
26454        let done_edit_rx = done_edit_rx.take().unwrap();
26455        async move {
26456            done_edit_rx.await.unwrap();
26457            Ok(None)
26458        }
26459    });
26460
26461    start_edit_rx.await.unwrap();
26462    buffer2
26463        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
26464        .unwrap();
26465
26466    done_edit_tx.send(()).unwrap();
26467
26468    save.await.unwrap();
26469    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
26470}
26471
26472#[track_caller]
26473fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
26474    editor
26475        .all_inlays(cx)
26476        .into_iter()
26477        .filter_map(|inlay| inlay.get_color())
26478        .map(Rgba::from)
26479        .collect()
26480}
26481
26482#[gpui::test]
26483fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
26484    init_test(cx, |_| {});
26485
26486    let editor = cx.add_window(|window, cx| {
26487        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
26488        build_editor(buffer, window, cx)
26489    });
26490
26491    editor
26492        .update(cx, |editor, window, cx| {
26493            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26494                s.select_display_ranges([
26495                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
26496                ])
26497            });
26498
26499            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
26500
26501            assert_eq!(
26502                editor.display_text(cx),
26503                "line1\nline2\nline2",
26504                "Duplicating last line upward should create duplicate above, not on same line"
26505            );
26506
26507            assert_eq!(
26508                editor.selections.display_ranges(cx),
26509                vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)],
26510                "Selection should remain on the original line"
26511            );
26512        })
26513        .unwrap();
26514}
26515
26516#[gpui::test]
26517async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
26518    init_test(cx, |_| {});
26519
26520    let mut cx = EditorTestContext::new(cx).await;
26521
26522    cx.set_state("line1\nline2ˇ");
26523
26524    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
26525
26526    let clipboard_text = cx
26527        .read_from_clipboard()
26528        .and_then(|item| item.text().as_deref().map(str::to_string));
26529
26530    assert_eq!(
26531        clipboard_text,
26532        Some("line2\n".to_string()),
26533        "Copying a line without trailing newline should include a newline"
26534    );
26535
26536    cx.set_state("line1\nˇ");
26537
26538    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
26539
26540    cx.assert_editor_state("line1\nline2\nˇ");
26541}