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;
   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::<f32>::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::<f32>::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_f32())
  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    });
 1260}
 1261
 1262#[gpui::test]
 1263fn test_move_cursor(cx: &mut TestAppContext) {
 1264    init_test(cx, |_| {});
 1265
 1266    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1267    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1268
 1269    buffer.update(cx, |buffer, cx| {
 1270        buffer.edit(
 1271            vec![
 1272                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1273                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1274            ],
 1275            None,
 1276            cx,
 1277        );
 1278    });
 1279    _ = editor.update(cx, |editor, window, cx| {
 1280        assert_eq!(
 1281            editor.selections.display_ranges(cx),
 1282            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1283        );
 1284
 1285        editor.move_down(&MoveDown, window, cx);
 1286        assert_eq!(
 1287            editor.selections.display_ranges(cx),
 1288            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1289        );
 1290
 1291        editor.move_right(&MoveRight, window, cx);
 1292        assert_eq!(
 1293            editor.selections.display_ranges(cx),
 1294            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1295        );
 1296
 1297        editor.move_left(&MoveLeft, window, cx);
 1298        assert_eq!(
 1299            editor.selections.display_ranges(cx),
 1300            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1301        );
 1302
 1303        editor.move_up(&MoveUp, window, cx);
 1304        assert_eq!(
 1305            editor.selections.display_ranges(cx),
 1306            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1307        );
 1308
 1309        editor.move_to_end(&MoveToEnd, window, cx);
 1310        assert_eq!(
 1311            editor.selections.display_ranges(cx),
 1312            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1313        );
 1314
 1315        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1316        assert_eq!(
 1317            editor.selections.display_ranges(cx),
 1318            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1319        );
 1320
 1321        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1322            s.select_display_ranges([
 1323                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1324            ]);
 1325        });
 1326        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1327        assert_eq!(
 1328            editor.selections.display_ranges(cx),
 1329            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1330        );
 1331
 1332        editor.select_to_end(&SelectToEnd, window, cx);
 1333        assert_eq!(
 1334            editor.selections.display_ranges(cx),
 1335            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1336        );
 1337    });
 1338}
 1339
 1340#[gpui::test]
 1341fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1342    init_test(cx, |_| {});
 1343
 1344    let editor = cx.add_window(|window, cx| {
 1345        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1346        build_editor(buffer, window, cx)
 1347    });
 1348
 1349    assert_eq!('🟥'.len_utf8(), 4);
 1350    assert_eq!('α'.len_utf8(), 2);
 1351
 1352    _ = editor.update(cx, |editor, window, cx| {
 1353        editor.fold_creases(
 1354            vec![
 1355                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1356                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1357                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1358            ],
 1359            true,
 1360            window,
 1361            cx,
 1362        );
 1363        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1364
 1365        editor.move_right(&MoveRight, window, cx);
 1366        assert_eq!(
 1367            editor.selections.display_ranges(cx),
 1368            &[empty_range(0, "🟥".len())]
 1369        );
 1370        editor.move_right(&MoveRight, window, cx);
 1371        assert_eq!(
 1372            editor.selections.display_ranges(cx),
 1373            &[empty_range(0, "🟥🟧".len())]
 1374        );
 1375        editor.move_right(&MoveRight, window, cx);
 1376        assert_eq!(
 1377            editor.selections.display_ranges(cx),
 1378            &[empty_range(0, "🟥🟧⋯".len())]
 1379        );
 1380
 1381        editor.move_down(&MoveDown, window, cx);
 1382        assert_eq!(
 1383            editor.selections.display_ranges(cx),
 1384            &[empty_range(1, "ab⋯e".len())]
 1385        );
 1386        editor.move_left(&MoveLeft, window, cx);
 1387        assert_eq!(
 1388            editor.selections.display_ranges(cx),
 1389            &[empty_range(1, "ab⋯".len())]
 1390        );
 1391        editor.move_left(&MoveLeft, window, cx);
 1392        assert_eq!(
 1393            editor.selections.display_ranges(cx),
 1394            &[empty_range(1, "ab".len())]
 1395        );
 1396        editor.move_left(&MoveLeft, window, cx);
 1397        assert_eq!(
 1398            editor.selections.display_ranges(cx),
 1399            &[empty_range(1, "a".len())]
 1400        );
 1401
 1402        editor.move_down(&MoveDown, window, cx);
 1403        assert_eq!(
 1404            editor.selections.display_ranges(cx),
 1405            &[empty_range(2, "α".len())]
 1406        );
 1407        editor.move_right(&MoveRight, window, cx);
 1408        assert_eq!(
 1409            editor.selections.display_ranges(cx),
 1410            &[empty_range(2, "αβ".len())]
 1411        );
 1412        editor.move_right(&MoveRight, window, cx);
 1413        assert_eq!(
 1414            editor.selections.display_ranges(cx),
 1415            &[empty_range(2, "αβ⋯".len())]
 1416        );
 1417        editor.move_right(&MoveRight, window, cx);
 1418        assert_eq!(
 1419            editor.selections.display_ranges(cx),
 1420            &[empty_range(2, "αβ⋯ε".len())]
 1421        );
 1422
 1423        editor.move_up(&MoveUp, window, cx);
 1424        assert_eq!(
 1425            editor.selections.display_ranges(cx),
 1426            &[empty_range(1, "ab⋯e".len())]
 1427        );
 1428        editor.move_down(&MoveDown, window, cx);
 1429        assert_eq!(
 1430            editor.selections.display_ranges(cx),
 1431            &[empty_range(2, "αβ⋯ε".len())]
 1432        );
 1433        editor.move_up(&MoveUp, window, cx);
 1434        assert_eq!(
 1435            editor.selections.display_ranges(cx),
 1436            &[empty_range(1, "ab⋯e".len())]
 1437        );
 1438
 1439        editor.move_up(&MoveUp, window, cx);
 1440        assert_eq!(
 1441            editor.selections.display_ranges(cx),
 1442            &[empty_range(0, "🟥🟧".len())]
 1443        );
 1444        editor.move_left(&MoveLeft, window, cx);
 1445        assert_eq!(
 1446            editor.selections.display_ranges(cx),
 1447            &[empty_range(0, "🟥".len())]
 1448        );
 1449        editor.move_left(&MoveLeft, window, cx);
 1450        assert_eq!(
 1451            editor.selections.display_ranges(cx),
 1452            &[empty_range(0, "".len())]
 1453        );
 1454    });
 1455}
 1456
 1457#[gpui::test]
 1458fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1459    init_test(cx, |_| {});
 1460
 1461    let editor = cx.add_window(|window, cx| {
 1462        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1463        build_editor(buffer, window, cx)
 1464    });
 1465    _ = editor.update(cx, |editor, window, cx| {
 1466        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1467            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1468        });
 1469
 1470        // moving above start of document should move selection to start of document,
 1471        // but the next move down should still be at the original goal_x
 1472        editor.move_up(&MoveUp, window, cx);
 1473        assert_eq!(
 1474            editor.selections.display_ranges(cx),
 1475            &[empty_range(0, "".len())]
 1476        );
 1477
 1478        editor.move_down(&MoveDown, window, cx);
 1479        assert_eq!(
 1480            editor.selections.display_ranges(cx),
 1481            &[empty_range(1, "abcd".len())]
 1482        );
 1483
 1484        editor.move_down(&MoveDown, window, cx);
 1485        assert_eq!(
 1486            editor.selections.display_ranges(cx),
 1487            &[empty_range(2, "αβγ".len())]
 1488        );
 1489
 1490        editor.move_down(&MoveDown, window, cx);
 1491        assert_eq!(
 1492            editor.selections.display_ranges(cx),
 1493            &[empty_range(3, "abcd".len())]
 1494        );
 1495
 1496        editor.move_down(&MoveDown, window, cx);
 1497        assert_eq!(
 1498            editor.selections.display_ranges(cx),
 1499            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1500        );
 1501
 1502        // moving past end of document should not change goal_x
 1503        editor.move_down(&MoveDown, window, cx);
 1504        assert_eq!(
 1505            editor.selections.display_ranges(cx),
 1506            &[empty_range(5, "".len())]
 1507        );
 1508
 1509        editor.move_down(&MoveDown, window, cx);
 1510        assert_eq!(
 1511            editor.selections.display_ranges(cx),
 1512            &[empty_range(5, "".len())]
 1513        );
 1514
 1515        editor.move_up(&MoveUp, window, cx);
 1516        assert_eq!(
 1517            editor.selections.display_ranges(cx),
 1518            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1519        );
 1520
 1521        editor.move_up(&MoveUp, window, cx);
 1522        assert_eq!(
 1523            editor.selections.display_ranges(cx),
 1524            &[empty_range(3, "abcd".len())]
 1525        );
 1526
 1527        editor.move_up(&MoveUp, window, cx);
 1528        assert_eq!(
 1529            editor.selections.display_ranges(cx),
 1530            &[empty_range(2, "αβγ".len())]
 1531        );
 1532    });
 1533}
 1534
 1535#[gpui::test]
 1536fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1537    init_test(cx, |_| {});
 1538    let move_to_beg = MoveToBeginningOfLine {
 1539        stop_at_soft_wraps: true,
 1540        stop_at_indent: true,
 1541    };
 1542
 1543    let delete_to_beg = DeleteToBeginningOfLine {
 1544        stop_at_indent: false,
 1545    };
 1546
 1547    let move_to_end = MoveToEndOfLine {
 1548        stop_at_soft_wraps: true,
 1549    };
 1550
 1551    let editor = cx.add_window(|window, cx| {
 1552        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1553        build_editor(buffer, window, cx)
 1554    });
 1555    _ = editor.update(cx, |editor, window, cx| {
 1556        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1557            s.select_display_ranges([
 1558                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1559                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1560            ]);
 1561        });
 1562    });
 1563
 1564    _ = editor.update(cx, |editor, window, cx| {
 1565        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1566        assert_eq!(
 1567            editor.selections.display_ranges(cx),
 1568            &[
 1569                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1570                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1571            ]
 1572        );
 1573    });
 1574
 1575    _ = editor.update(cx, |editor, window, cx| {
 1576        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1577        assert_eq!(
 1578            editor.selections.display_ranges(cx),
 1579            &[
 1580                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1581                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1582            ]
 1583        );
 1584    });
 1585
 1586    _ = editor.update(cx, |editor, window, cx| {
 1587        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1588        assert_eq!(
 1589            editor.selections.display_ranges(cx),
 1590            &[
 1591                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1592                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1593            ]
 1594        );
 1595    });
 1596
 1597    _ = editor.update(cx, |editor, window, cx| {
 1598        editor.move_to_end_of_line(&move_to_end, window, cx);
 1599        assert_eq!(
 1600            editor.selections.display_ranges(cx),
 1601            &[
 1602                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1603                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1604            ]
 1605        );
 1606    });
 1607
 1608    // Moving to the end of line again is a no-op.
 1609    _ = editor.update(cx, |editor, window, cx| {
 1610        editor.move_to_end_of_line(&move_to_end, window, cx);
 1611        assert_eq!(
 1612            editor.selections.display_ranges(cx),
 1613            &[
 1614                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1615                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1616            ]
 1617        );
 1618    });
 1619
 1620    _ = editor.update(cx, |editor, window, cx| {
 1621        editor.move_left(&MoveLeft, window, cx);
 1622        editor.select_to_beginning_of_line(
 1623            &SelectToBeginningOfLine {
 1624                stop_at_soft_wraps: true,
 1625                stop_at_indent: true,
 1626            },
 1627            window,
 1628            cx,
 1629        );
 1630        assert_eq!(
 1631            editor.selections.display_ranges(cx),
 1632            &[
 1633                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1634                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1635            ]
 1636        );
 1637    });
 1638
 1639    _ = editor.update(cx, |editor, window, cx| {
 1640        editor.select_to_beginning_of_line(
 1641            &SelectToBeginningOfLine {
 1642                stop_at_soft_wraps: true,
 1643                stop_at_indent: true,
 1644            },
 1645            window,
 1646            cx,
 1647        );
 1648        assert_eq!(
 1649            editor.selections.display_ranges(cx),
 1650            &[
 1651                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1652                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1653            ]
 1654        );
 1655    });
 1656
 1657    _ = editor.update(cx, |editor, window, cx| {
 1658        editor.select_to_beginning_of_line(
 1659            &SelectToBeginningOfLine {
 1660                stop_at_soft_wraps: true,
 1661                stop_at_indent: true,
 1662            },
 1663            window,
 1664            cx,
 1665        );
 1666        assert_eq!(
 1667            editor.selections.display_ranges(cx),
 1668            &[
 1669                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1670                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1671            ]
 1672        );
 1673    });
 1674
 1675    _ = editor.update(cx, |editor, window, cx| {
 1676        editor.select_to_end_of_line(
 1677            &SelectToEndOfLine {
 1678                stop_at_soft_wraps: true,
 1679            },
 1680            window,
 1681            cx,
 1682        );
 1683        assert_eq!(
 1684            editor.selections.display_ranges(cx),
 1685            &[
 1686                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1687                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1688            ]
 1689        );
 1690    });
 1691
 1692    _ = editor.update(cx, |editor, window, cx| {
 1693        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1694        assert_eq!(editor.display_text(cx), "ab\n  de");
 1695        assert_eq!(
 1696            editor.selections.display_ranges(cx),
 1697            &[
 1698                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1699                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1700            ]
 1701        );
 1702    });
 1703
 1704    _ = editor.update(cx, |editor, window, cx| {
 1705        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1706        assert_eq!(editor.display_text(cx), "\n");
 1707        assert_eq!(
 1708            editor.selections.display_ranges(cx),
 1709            &[
 1710                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1711                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1712            ]
 1713        );
 1714    });
 1715}
 1716
 1717#[gpui::test]
 1718fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1719    init_test(cx, |_| {});
 1720    let move_to_beg = MoveToBeginningOfLine {
 1721        stop_at_soft_wraps: false,
 1722        stop_at_indent: false,
 1723    };
 1724
 1725    let move_to_end = MoveToEndOfLine {
 1726        stop_at_soft_wraps: false,
 1727    };
 1728
 1729    let editor = cx.add_window(|window, cx| {
 1730        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1731        build_editor(buffer, window, cx)
 1732    });
 1733
 1734    _ = editor.update(cx, |editor, window, cx| {
 1735        editor.set_wrap_width(Some(140.0.into()), cx);
 1736
 1737        // We expect the following lines after wrapping
 1738        // ```
 1739        // thequickbrownfox
 1740        // jumpedoverthelazydo
 1741        // gs
 1742        // ```
 1743        // The final `gs` was soft-wrapped onto a new line.
 1744        assert_eq!(
 1745            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1746            editor.display_text(cx),
 1747        );
 1748
 1749        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1750        // Start the cursor at the `k` on the first line
 1751        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1752            s.select_display_ranges([
 1753                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1754            ]);
 1755        });
 1756
 1757        // Moving to the beginning of the line should put us at the beginning of the line.
 1758        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1759        assert_eq!(
 1760            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1761            editor.selections.display_ranges(cx)
 1762        );
 1763
 1764        // Moving to the end of the line should put us at the end of the line.
 1765        editor.move_to_end_of_line(&move_to_end, window, cx);
 1766        assert_eq!(
 1767            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1768            editor.selections.display_ranges(cx)
 1769        );
 1770
 1771        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1772        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1773        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1774            s.select_display_ranges([
 1775                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1776            ]);
 1777        });
 1778
 1779        // Moving to the beginning of the line should put us at the start of the second line of
 1780        // display text, i.e., the `j`.
 1781        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1782        assert_eq!(
 1783            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1784            editor.selections.display_ranges(cx)
 1785        );
 1786
 1787        // Moving to the beginning of the line again should be a no-op.
 1788        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1789        assert_eq!(
 1790            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1791            editor.selections.display_ranges(cx)
 1792        );
 1793
 1794        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1795        // next display line.
 1796        editor.move_to_end_of_line(&move_to_end, window, cx);
 1797        assert_eq!(
 1798            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1799            editor.selections.display_ranges(cx)
 1800        );
 1801
 1802        // Moving to the end of the line again should be a no-op.
 1803        editor.move_to_end_of_line(&move_to_end, window, cx);
 1804        assert_eq!(
 1805            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1806            editor.selections.display_ranges(cx)
 1807        );
 1808    });
 1809}
 1810
 1811#[gpui::test]
 1812fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1813    init_test(cx, |_| {});
 1814
 1815    let move_to_beg = MoveToBeginningOfLine {
 1816        stop_at_soft_wraps: true,
 1817        stop_at_indent: true,
 1818    };
 1819
 1820    let select_to_beg = SelectToBeginningOfLine {
 1821        stop_at_soft_wraps: true,
 1822        stop_at_indent: true,
 1823    };
 1824
 1825    let delete_to_beg = DeleteToBeginningOfLine {
 1826        stop_at_indent: true,
 1827    };
 1828
 1829    let move_to_end = MoveToEndOfLine {
 1830        stop_at_soft_wraps: false,
 1831    };
 1832
 1833    let editor = cx.add_window(|window, cx| {
 1834        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1835        build_editor(buffer, window, cx)
 1836    });
 1837
 1838    _ = editor.update(cx, |editor, window, cx| {
 1839        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1840            s.select_display_ranges([
 1841                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1842                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1843            ]);
 1844        });
 1845
 1846        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1847        // and the second cursor at the first non-whitespace character in the line.
 1848        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1849        assert_eq!(
 1850            editor.selections.display_ranges(cx),
 1851            &[
 1852                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1853                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1854            ]
 1855        );
 1856
 1857        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1858        // and should move the second cursor to the beginning of the line.
 1859        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1860        assert_eq!(
 1861            editor.selections.display_ranges(cx),
 1862            &[
 1863                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1864                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1865            ]
 1866        );
 1867
 1868        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1869        // and should move the second cursor back to the first non-whitespace character in the line.
 1870        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1871        assert_eq!(
 1872            editor.selections.display_ranges(cx),
 1873            &[
 1874                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1875                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1876            ]
 1877        );
 1878
 1879        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1880        // and to the first non-whitespace character in the line for the second cursor.
 1881        editor.move_to_end_of_line(&move_to_end, window, cx);
 1882        editor.move_left(&MoveLeft, window, cx);
 1883        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1884        assert_eq!(
 1885            editor.selections.display_ranges(cx),
 1886            &[
 1887                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1888                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1889            ]
 1890        );
 1891
 1892        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1893        // and should select to the beginning of the line for the second cursor.
 1894        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1895        assert_eq!(
 1896            editor.selections.display_ranges(cx),
 1897            &[
 1898                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1899                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1900            ]
 1901        );
 1902
 1903        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1904        // and should delete to the first non-whitespace character in the line for the second cursor.
 1905        editor.move_to_end_of_line(&move_to_end, window, cx);
 1906        editor.move_left(&MoveLeft, window, cx);
 1907        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1908        assert_eq!(editor.text(cx), "c\n  f");
 1909    });
 1910}
 1911
 1912#[gpui::test]
 1913fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 1914    init_test(cx, |_| {});
 1915
 1916    let move_to_beg = MoveToBeginningOfLine {
 1917        stop_at_soft_wraps: true,
 1918        stop_at_indent: true,
 1919    };
 1920
 1921    let editor = cx.add_window(|window, cx| {
 1922        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 1923        build_editor(buffer, window, cx)
 1924    });
 1925
 1926    _ = editor.update(cx, |editor, window, cx| {
 1927        // test cursor between line_start and indent_start
 1928        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1929            s.select_display_ranges([
 1930                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 1931            ]);
 1932        });
 1933
 1934        // cursor should move to line_start
 1935        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1936        assert_eq!(
 1937            editor.selections.display_ranges(cx),
 1938            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1939        );
 1940
 1941        // cursor should move to indent_start
 1942        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1943        assert_eq!(
 1944            editor.selections.display_ranges(cx),
 1945            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 1946        );
 1947
 1948        // cursor should move to back to line_start
 1949        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1950        assert_eq!(
 1951            editor.selections.display_ranges(cx),
 1952            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1953        );
 1954    });
 1955}
 1956
 1957#[gpui::test]
 1958fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1959    init_test(cx, |_| {});
 1960
 1961    let editor = cx.add_window(|window, cx| {
 1962        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1963        build_editor(buffer, window, cx)
 1964    });
 1965    _ = editor.update(cx, |editor, window, cx| {
 1966        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1967            s.select_display_ranges([
 1968                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1969                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1970            ])
 1971        });
 1972        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1973        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1974
 1975        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1976        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1977
 1978        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1979        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1980
 1981        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1982        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1983
 1984        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1985        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 1986
 1987        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1988        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1989
 1990        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1991        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1992
 1993        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1994        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1995
 1996        editor.move_right(&MoveRight, window, cx);
 1997        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1998        assert_selection_ranges(
 1999            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 2000            editor,
 2001            cx,
 2002        );
 2003
 2004        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2005        assert_selection_ranges(
 2006            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2007            editor,
 2008            cx,
 2009        );
 2010
 2011        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2012        assert_selection_ranges(
 2013            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2014            editor,
 2015            cx,
 2016        );
 2017    });
 2018}
 2019
 2020#[gpui::test]
 2021fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2022    init_test(cx, |_| {});
 2023
 2024    let editor = cx.add_window(|window, cx| {
 2025        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2026        build_editor(buffer, window, cx)
 2027    });
 2028
 2029    _ = editor.update(cx, |editor, window, cx| {
 2030        editor.set_wrap_width(Some(140.0.into()), cx);
 2031        assert_eq!(
 2032            editor.display_text(cx),
 2033            "use one::{\n    two::three::\n    four::five\n};"
 2034        );
 2035
 2036        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2037            s.select_display_ranges([
 2038                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2039            ]);
 2040        });
 2041
 2042        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2043        assert_eq!(
 2044            editor.selections.display_ranges(cx),
 2045            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2046        );
 2047
 2048        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2049        assert_eq!(
 2050            editor.selections.display_ranges(cx),
 2051            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2052        );
 2053
 2054        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2055        assert_eq!(
 2056            editor.selections.display_ranges(cx),
 2057            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2058        );
 2059
 2060        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2061        assert_eq!(
 2062            editor.selections.display_ranges(cx),
 2063            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2064        );
 2065
 2066        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2067        assert_eq!(
 2068            editor.selections.display_ranges(cx),
 2069            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2070        );
 2071
 2072        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2073        assert_eq!(
 2074            editor.selections.display_ranges(cx),
 2075            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2076        );
 2077    });
 2078}
 2079
 2080#[gpui::test]
 2081async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2082    init_test(cx, |_| {});
 2083    let mut cx = EditorTestContext::new(cx).await;
 2084
 2085    let line_height = cx.editor(|editor, window, _| {
 2086        editor
 2087            .style()
 2088            .unwrap()
 2089            .text
 2090            .line_height_in_pixels(window.rem_size())
 2091    });
 2092    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2093
 2094    cx.set_state(
 2095        &r#"ˇone
 2096        two
 2097
 2098        three
 2099        fourˇ
 2100        five
 2101
 2102        six"#
 2103            .unindent(),
 2104    );
 2105
 2106    cx.update_editor(|editor, window, cx| {
 2107        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2108    });
 2109    cx.assert_editor_state(
 2110        &r#"one
 2111        two
 2112        ˇ
 2113        three
 2114        four
 2115        five
 2116        ˇ
 2117        six"#
 2118            .unindent(),
 2119    );
 2120
 2121    cx.update_editor(|editor, window, cx| {
 2122        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2123    });
 2124    cx.assert_editor_state(
 2125        &r#"one
 2126        two
 2127
 2128        three
 2129        four
 2130        five
 2131        ˇ
 2132        sixˇ"#
 2133            .unindent(),
 2134    );
 2135
 2136    cx.update_editor(|editor, window, cx| {
 2137        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2138    });
 2139    cx.assert_editor_state(
 2140        &r#"one
 2141        two
 2142
 2143        three
 2144        four
 2145        five
 2146
 2147        sixˇ"#
 2148            .unindent(),
 2149    );
 2150
 2151    cx.update_editor(|editor, window, cx| {
 2152        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2153    });
 2154    cx.assert_editor_state(
 2155        &r#"one
 2156        two
 2157
 2158        three
 2159        four
 2160        five
 2161        ˇ
 2162        six"#
 2163            .unindent(),
 2164    );
 2165
 2166    cx.update_editor(|editor, window, cx| {
 2167        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2168    });
 2169    cx.assert_editor_state(
 2170        &r#"one
 2171        two
 2172        ˇ
 2173        three
 2174        four
 2175        five
 2176
 2177        six"#
 2178            .unindent(),
 2179    );
 2180
 2181    cx.update_editor(|editor, window, cx| {
 2182        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2183    });
 2184    cx.assert_editor_state(
 2185        &r#"ˇone
 2186        two
 2187
 2188        three
 2189        four
 2190        five
 2191
 2192        six"#
 2193            .unindent(),
 2194    );
 2195}
 2196
 2197#[gpui::test]
 2198async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2199    init_test(cx, |_| {});
 2200    let mut cx = EditorTestContext::new(cx).await;
 2201    let line_height = cx.editor(|editor, window, _| {
 2202        editor
 2203            .style()
 2204            .unwrap()
 2205            .text
 2206            .line_height_in_pixels(window.rem_size())
 2207    });
 2208    let window = cx.window;
 2209    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2210
 2211    cx.set_state(
 2212        r#"ˇone
 2213        two
 2214        three
 2215        four
 2216        five
 2217        six
 2218        seven
 2219        eight
 2220        nine
 2221        ten
 2222        "#,
 2223    );
 2224
 2225    cx.update_editor(|editor, window, cx| {
 2226        assert_eq!(
 2227            editor.snapshot(window, cx).scroll_position(),
 2228            gpui::Point::new(0., 0.)
 2229        );
 2230        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2231        assert_eq!(
 2232            editor.snapshot(window, cx).scroll_position(),
 2233            gpui::Point::new(0., 3.)
 2234        );
 2235        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2236        assert_eq!(
 2237            editor.snapshot(window, cx).scroll_position(),
 2238            gpui::Point::new(0., 6.)
 2239        );
 2240        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2241        assert_eq!(
 2242            editor.snapshot(window, cx).scroll_position(),
 2243            gpui::Point::new(0., 3.)
 2244        );
 2245
 2246        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2247        assert_eq!(
 2248            editor.snapshot(window, cx).scroll_position(),
 2249            gpui::Point::new(0., 1.)
 2250        );
 2251        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2252        assert_eq!(
 2253            editor.snapshot(window, cx).scroll_position(),
 2254            gpui::Point::new(0., 3.)
 2255        );
 2256    });
 2257}
 2258
 2259#[gpui::test]
 2260async fn test_autoscroll(cx: &mut TestAppContext) {
 2261    init_test(cx, |_| {});
 2262    let mut cx = EditorTestContext::new(cx).await;
 2263
 2264    let line_height = cx.update_editor(|editor, window, cx| {
 2265        editor.set_vertical_scroll_margin(2, cx);
 2266        editor
 2267            .style()
 2268            .unwrap()
 2269            .text
 2270            .line_height_in_pixels(window.rem_size())
 2271    });
 2272    let window = cx.window;
 2273    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2274
 2275    cx.set_state(
 2276        r#"ˇone
 2277            two
 2278            three
 2279            four
 2280            five
 2281            six
 2282            seven
 2283            eight
 2284            nine
 2285            ten
 2286        "#,
 2287    );
 2288    cx.update_editor(|editor, window, cx| {
 2289        assert_eq!(
 2290            editor.snapshot(window, cx).scroll_position(),
 2291            gpui::Point::new(0., 0.0)
 2292        );
 2293    });
 2294
 2295    // Add a cursor below the visible area. Since both cursors cannot fit
 2296    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2297    // allows the vertical scroll margin below that cursor.
 2298    cx.update_editor(|editor, window, cx| {
 2299        editor.change_selections(Default::default(), window, cx, |selections| {
 2300            selections.select_ranges([
 2301                Point::new(0, 0)..Point::new(0, 0),
 2302                Point::new(6, 0)..Point::new(6, 0),
 2303            ]);
 2304        })
 2305    });
 2306    cx.update_editor(|editor, window, cx| {
 2307        assert_eq!(
 2308            editor.snapshot(window, cx).scroll_position(),
 2309            gpui::Point::new(0., 3.0)
 2310        );
 2311    });
 2312
 2313    // Move down. The editor cursor scrolls down to track the newest cursor.
 2314    cx.update_editor(|editor, window, cx| {
 2315        editor.move_down(&Default::default(), window, cx);
 2316    });
 2317    cx.update_editor(|editor, window, cx| {
 2318        assert_eq!(
 2319            editor.snapshot(window, cx).scroll_position(),
 2320            gpui::Point::new(0., 4.0)
 2321        );
 2322    });
 2323
 2324    // Add a cursor above the visible area. Since both cursors fit on screen,
 2325    // the editor scrolls to show both.
 2326    cx.update_editor(|editor, window, cx| {
 2327        editor.change_selections(Default::default(), window, cx, |selections| {
 2328            selections.select_ranges([
 2329                Point::new(1, 0)..Point::new(1, 0),
 2330                Point::new(6, 0)..Point::new(6, 0),
 2331            ]);
 2332        })
 2333    });
 2334    cx.update_editor(|editor, window, cx| {
 2335        assert_eq!(
 2336            editor.snapshot(window, cx).scroll_position(),
 2337            gpui::Point::new(0., 1.0)
 2338        );
 2339    });
 2340}
 2341
 2342#[gpui::test]
 2343async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2344    init_test(cx, |_| {});
 2345    let mut cx = EditorTestContext::new(cx).await;
 2346
 2347    let line_height = cx.editor(|editor, window, _cx| {
 2348        editor
 2349            .style()
 2350            .unwrap()
 2351            .text
 2352            .line_height_in_pixels(window.rem_size())
 2353    });
 2354    let window = cx.window;
 2355    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2356    cx.set_state(
 2357        &r#"
 2358        ˇone
 2359        two
 2360        threeˇ
 2361        four
 2362        five
 2363        six
 2364        seven
 2365        eight
 2366        nine
 2367        ten
 2368        "#
 2369        .unindent(),
 2370    );
 2371
 2372    cx.update_editor(|editor, window, cx| {
 2373        editor.move_page_down(&MovePageDown::default(), window, cx)
 2374    });
 2375    cx.assert_editor_state(
 2376        &r#"
 2377        one
 2378        two
 2379        three
 2380        ˇfour
 2381        five
 2382        sixˇ
 2383        seven
 2384        eight
 2385        nine
 2386        ten
 2387        "#
 2388        .unindent(),
 2389    );
 2390
 2391    cx.update_editor(|editor, window, cx| {
 2392        editor.move_page_down(&MovePageDown::default(), window, cx)
 2393    });
 2394    cx.assert_editor_state(
 2395        &r#"
 2396        one
 2397        two
 2398        three
 2399        four
 2400        five
 2401        six
 2402        ˇseven
 2403        eight
 2404        nineˇ
 2405        ten
 2406        "#
 2407        .unindent(),
 2408    );
 2409
 2410    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2411    cx.assert_editor_state(
 2412        &r#"
 2413        one
 2414        two
 2415        three
 2416        ˇfour
 2417        five
 2418        sixˇ
 2419        seven
 2420        eight
 2421        nine
 2422        ten
 2423        "#
 2424        .unindent(),
 2425    );
 2426
 2427    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2428    cx.assert_editor_state(
 2429        &r#"
 2430        ˇone
 2431        two
 2432        threeˇ
 2433        four
 2434        five
 2435        six
 2436        seven
 2437        eight
 2438        nine
 2439        ten
 2440        "#
 2441        .unindent(),
 2442    );
 2443
 2444    // Test select collapsing
 2445    cx.update_editor(|editor, window, cx| {
 2446        editor.move_page_down(&MovePageDown::default(), window, cx);
 2447        editor.move_page_down(&MovePageDown::default(), window, cx);
 2448        editor.move_page_down(&MovePageDown::default(), window, cx);
 2449    });
 2450    cx.assert_editor_state(
 2451        &r#"
 2452        one
 2453        two
 2454        three
 2455        four
 2456        five
 2457        six
 2458        seven
 2459        eight
 2460        nine
 2461        ˇten
 2462        ˇ"#
 2463        .unindent(),
 2464    );
 2465}
 2466
 2467#[gpui::test]
 2468async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2469    init_test(cx, |_| {});
 2470    let mut cx = EditorTestContext::new(cx).await;
 2471    cx.set_state("one «two threeˇ» four");
 2472    cx.update_editor(|editor, window, cx| {
 2473        editor.delete_to_beginning_of_line(
 2474            &DeleteToBeginningOfLine {
 2475                stop_at_indent: false,
 2476            },
 2477            window,
 2478            cx,
 2479        );
 2480        assert_eq!(editor.text(cx), " four");
 2481    });
 2482}
 2483
 2484#[gpui::test]
 2485async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2486    init_test(cx, |_| {});
 2487
 2488    let mut cx = EditorTestContext::new(cx).await;
 2489
 2490    // For an empty selection, the preceding word fragment is deleted.
 2491    // For non-empty selections, only selected characters are deleted.
 2492    cx.set_state("onˇe two t«hreˇ»e four");
 2493    cx.update_editor(|editor, window, cx| {
 2494        editor.delete_to_previous_word_start(
 2495            &DeleteToPreviousWordStart {
 2496                ignore_newlines: false,
 2497                ignore_brackets: false,
 2498            },
 2499            window,
 2500            cx,
 2501        );
 2502    });
 2503    cx.assert_editor_state("ˇe two tˇe four");
 2504
 2505    cx.set_state("e tˇwo te «fˇ»our");
 2506    cx.update_editor(|editor, window, cx| {
 2507        editor.delete_to_next_word_end(
 2508            &DeleteToNextWordEnd {
 2509                ignore_newlines: false,
 2510                ignore_brackets: false,
 2511            },
 2512            window,
 2513            cx,
 2514        );
 2515    });
 2516    cx.assert_editor_state("e tˇ te ˇour");
 2517}
 2518
 2519#[gpui::test]
 2520async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2521    init_test(cx, |_| {});
 2522
 2523    let mut cx = EditorTestContext::new(cx).await;
 2524
 2525    cx.set_state("here is some text    ˇwith a space");
 2526    cx.update_editor(|editor, window, cx| {
 2527        editor.delete_to_previous_word_start(
 2528            &DeleteToPreviousWordStart {
 2529                ignore_newlines: false,
 2530                ignore_brackets: true,
 2531            },
 2532            window,
 2533            cx,
 2534        );
 2535    });
 2536    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 2537    cx.assert_editor_state("here is some textˇwith a space");
 2538
 2539    cx.set_state("here is some text    ˇwith a space");
 2540    cx.update_editor(|editor, window, cx| {
 2541        editor.delete_to_previous_word_start(
 2542            &DeleteToPreviousWordStart {
 2543                ignore_newlines: false,
 2544                ignore_brackets: false,
 2545            },
 2546            window,
 2547            cx,
 2548        );
 2549    });
 2550    cx.assert_editor_state("here is some textˇwith a space");
 2551
 2552    cx.set_state("here is some textˇ    with a space");
 2553    cx.update_editor(|editor, window, cx| {
 2554        editor.delete_to_next_word_end(
 2555            &DeleteToNextWordEnd {
 2556                ignore_newlines: false,
 2557                ignore_brackets: true,
 2558            },
 2559            window,
 2560            cx,
 2561        );
 2562    });
 2563    // Same happens in the other direction.
 2564    cx.assert_editor_state("here is some textˇwith a space");
 2565
 2566    cx.set_state("here is some textˇ    with a space");
 2567    cx.update_editor(|editor, window, cx| {
 2568        editor.delete_to_next_word_end(
 2569            &DeleteToNextWordEnd {
 2570                ignore_newlines: false,
 2571                ignore_brackets: false,
 2572            },
 2573            window,
 2574            cx,
 2575        );
 2576    });
 2577    cx.assert_editor_state("here is some textˇwith a space");
 2578
 2579    cx.set_state("here is some textˇ    with a space");
 2580    cx.update_editor(|editor, window, cx| {
 2581        editor.delete_to_next_word_end(
 2582            &DeleteToNextWordEnd {
 2583                ignore_newlines: true,
 2584                ignore_brackets: false,
 2585            },
 2586            window,
 2587            cx,
 2588        );
 2589    });
 2590    cx.assert_editor_state("here is some textˇwith a space");
 2591    cx.update_editor(|editor, window, cx| {
 2592        editor.delete_to_previous_word_start(
 2593            &DeleteToPreviousWordStart {
 2594                ignore_newlines: true,
 2595                ignore_brackets: false,
 2596            },
 2597            window,
 2598            cx,
 2599        );
 2600    });
 2601    cx.assert_editor_state("here is some ˇwith a space");
 2602    cx.update_editor(|editor, window, cx| {
 2603        editor.delete_to_previous_word_start(
 2604            &DeleteToPreviousWordStart {
 2605                ignore_newlines: true,
 2606                ignore_brackets: false,
 2607            },
 2608            window,
 2609            cx,
 2610        );
 2611    });
 2612    // Single whitespaces are removed with the word behind them.
 2613    cx.assert_editor_state("here is ˇwith a space");
 2614    cx.update_editor(|editor, window, cx| {
 2615        editor.delete_to_previous_word_start(
 2616            &DeleteToPreviousWordStart {
 2617                ignore_newlines: true,
 2618                ignore_brackets: false,
 2619            },
 2620            window,
 2621            cx,
 2622        );
 2623    });
 2624    cx.assert_editor_state("here ˇwith a space");
 2625    cx.update_editor(|editor, window, cx| {
 2626        editor.delete_to_previous_word_start(
 2627            &DeleteToPreviousWordStart {
 2628                ignore_newlines: true,
 2629                ignore_brackets: false,
 2630            },
 2631            window,
 2632            cx,
 2633        );
 2634    });
 2635    cx.assert_editor_state("ˇwith a space");
 2636    cx.update_editor(|editor, window, cx| {
 2637        editor.delete_to_previous_word_start(
 2638            &DeleteToPreviousWordStart {
 2639                ignore_newlines: true,
 2640                ignore_brackets: false,
 2641            },
 2642            window,
 2643            cx,
 2644        );
 2645    });
 2646    cx.assert_editor_state("ˇwith a space");
 2647    cx.update_editor(|editor, window, cx| {
 2648        editor.delete_to_next_word_end(
 2649            &DeleteToNextWordEnd {
 2650                ignore_newlines: true,
 2651                ignore_brackets: false,
 2652            },
 2653            window,
 2654            cx,
 2655        );
 2656    });
 2657    // Same happens in the other direction.
 2658    cx.assert_editor_state("ˇ a space");
 2659    cx.update_editor(|editor, window, cx| {
 2660        editor.delete_to_next_word_end(
 2661            &DeleteToNextWordEnd {
 2662                ignore_newlines: true,
 2663                ignore_brackets: false,
 2664            },
 2665            window,
 2666            cx,
 2667        );
 2668    });
 2669    cx.assert_editor_state("ˇ space");
 2670    cx.update_editor(|editor, window, cx| {
 2671        editor.delete_to_next_word_end(
 2672            &DeleteToNextWordEnd {
 2673                ignore_newlines: true,
 2674                ignore_brackets: false,
 2675            },
 2676            window,
 2677            cx,
 2678        );
 2679    });
 2680    cx.assert_editor_state("ˇ");
 2681    cx.update_editor(|editor, window, cx| {
 2682        editor.delete_to_next_word_end(
 2683            &DeleteToNextWordEnd {
 2684                ignore_newlines: true,
 2685                ignore_brackets: false,
 2686            },
 2687            window,
 2688            cx,
 2689        );
 2690    });
 2691    cx.assert_editor_state("ˇ");
 2692    cx.update_editor(|editor, window, cx| {
 2693        editor.delete_to_previous_word_start(
 2694            &DeleteToPreviousWordStart {
 2695                ignore_newlines: true,
 2696                ignore_brackets: false,
 2697            },
 2698            window,
 2699            cx,
 2700        );
 2701    });
 2702    cx.assert_editor_state("ˇ");
 2703}
 2704
 2705#[gpui::test]
 2706async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2707    init_test(cx, |_| {});
 2708
 2709    let language = Arc::new(
 2710        Language::new(
 2711            LanguageConfig {
 2712                brackets: BracketPairConfig {
 2713                    pairs: vec![
 2714                        BracketPair {
 2715                            start: "\"".to_string(),
 2716                            end: "\"".to_string(),
 2717                            close: true,
 2718                            surround: true,
 2719                            newline: false,
 2720                        },
 2721                        BracketPair {
 2722                            start: "(".to_string(),
 2723                            end: ")".to_string(),
 2724                            close: true,
 2725                            surround: true,
 2726                            newline: true,
 2727                        },
 2728                    ],
 2729                    ..BracketPairConfig::default()
 2730                },
 2731                ..LanguageConfig::default()
 2732            },
 2733            Some(tree_sitter_rust::LANGUAGE.into()),
 2734        )
 2735        .with_brackets_query(
 2736            r#"
 2737                ("(" @open ")" @close)
 2738                ("\"" @open "\"" @close)
 2739            "#,
 2740        )
 2741        .unwrap(),
 2742    );
 2743
 2744    let mut cx = EditorTestContext::new(cx).await;
 2745    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2746
 2747    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2748    cx.update_editor(|editor, window, cx| {
 2749        editor.delete_to_previous_word_start(
 2750            &DeleteToPreviousWordStart {
 2751                ignore_newlines: true,
 2752                ignore_brackets: false,
 2753            },
 2754            window,
 2755            cx,
 2756        );
 2757    });
 2758    // Deletion stops before brackets if asked to not ignore them.
 2759    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 2760    cx.update_editor(|editor, window, cx| {
 2761        editor.delete_to_previous_word_start(
 2762            &DeleteToPreviousWordStart {
 2763                ignore_newlines: true,
 2764                ignore_brackets: false,
 2765            },
 2766            window,
 2767            cx,
 2768        );
 2769    });
 2770    // Deletion has to remove a single bracket and then stop again.
 2771    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 2772
 2773    cx.update_editor(|editor, window, cx| {
 2774        editor.delete_to_previous_word_start(
 2775            &DeleteToPreviousWordStart {
 2776                ignore_newlines: true,
 2777                ignore_brackets: false,
 2778            },
 2779            window,
 2780            cx,
 2781        );
 2782    });
 2783    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 2784
 2785    cx.update_editor(|editor, window, cx| {
 2786        editor.delete_to_previous_word_start(
 2787            &DeleteToPreviousWordStart {
 2788                ignore_newlines: true,
 2789                ignore_brackets: false,
 2790            },
 2791            window,
 2792            cx,
 2793        );
 2794    });
 2795    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2796
 2797    cx.update_editor(|editor, window, cx| {
 2798        editor.delete_to_previous_word_start(
 2799            &DeleteToPreviousWordStart {
 2800                ignore_newlines: true,
 2801                ignore_brackets: false,
 2802            },
 2803            window,
 2804            cx,
 2805        );
 2806    });
 2807    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2808
 2809    cx.update_editor(|editor, window, cx| {
 2810        editor.delete_to_next_word_end(
 2811            &DeleteToNextWordEnd {
 2812                ignore_newlines: true,
 2813                ignore_brackets: false,
 2814            },
 2815            window,
 2816            cx,
 2817        );
 2818    });
 2819    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 2820    cx.assert_editor_state(r#"ˇ");"#);
 2821
 2822    cx.update_editor(|editor, window, cx| {
 2823        editor.delete_to_next_word_end(
 2824            &DeleteToNextWordEnd {
 2825                ignore_newlines: true,
 2826                ignore_brackets: false,
 2827            },
 2828            window,
 2829            cx,
 2830        );
 2831    });
 2832    cx.assert_editor_state(r#"ˇ"#);
 2833
 2834    cx.update_editor(|editor, window, cx| {
 2835        editor.delete_to_next_word_end(
 2836            &DeleteToNextWordEnd {
 2837                ignore_newlines: true,
 2838                ignore_brackets: false,
 2839            },
 2840            window,
 2841            cx,
 2842        );
 2843    });
 2844    cx.assert_editor_state(r#"ˇ"#);
 2845
 2846    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2847    cx.update_editor(|editor, window, cx| {
 2848        editor.delete_to_previous_word_start(
 2849            &DeleteToPreviousWordStart {
 2850                ignore_newlines: true,
 2851                ignore_brackets: true,
 2852            },
 2853            window,
 2854            cx,
 2855        );
 2856    });
 2857    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 2858}
 2859
 2860#[gpui::test]
 2861fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2862    init_test(cx, |_| {});
 2863
 2864    let editor = cx.add_window(|window, cx| {
 2865        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2866        build_editor(buffer, window, cx)
 2867    });
 2868    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2869        ignore_newlines: false,
 2870        ignore_brackets: false,
 2871    };
 2872    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2873        ignore_newlines: true,
 2874        ignore_brackets: false,
 2875    };
 2876
 2877    _ = editor.update(cx, |editor, window, cx| {
 2878        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2879            s.select_display_ranges([
 2880                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2881            ])
 2882        });
 2883        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2884        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2885        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2886        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2887        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2888        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2889        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2890        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2891        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2892        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2893        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2894        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2895    });
 2896}
 2897
 2898#[gpui::test]
 2899fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2900    init_test(cx, |_| {});
 2901
 2902    let editor = cx.add_window(|window, cx| {
 2903        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2904        build_editor(buffer, window, cx)
 2905    });
 2906    let del_to_next_word_end = DeleteToNextWordEnd {
 2907        ignore_newlines: false,
 2908        ignore_brackets: false,
 2909    };
 2910    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2911        ignore_newlines: true,
 2912        ignore_brackets: false,
 2913    };
 2914
 2915    _ = editor.update(cx, |editor, window, cx| {
 2916        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2917            s.select_display_ranges([
 2918                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2919            ])
 2920        });
 2921        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2922        assert_eq!(
 2923            editor.buffer.read(cx).read(cx).text(),
 2924            "one\n   two\nthree\n   four"
 2925        );
 2926        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2927        assert_eq!(
 2928            editor.buffer.read(cx).read(cx).text(),
 2929            "\n   two\nthree\n   four"
 2930        );
 2931        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2932        assert_eq!(
 2933            editor.buffer.read(cx).read(cx).text(),
 2934            "two\nthree\n   four"
 2935        );
 2936        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2937        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2938        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2939        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2940        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2941        assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
 2942        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2943        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2944    });
 2945}
 2946
 2947#[gpui::test]
 2948fn test_newline(cx: &mut TestAppContext) {
 2949    init_test(cx, |_| {});
 2950
 2951    let editor = cx.add_window(|window, cx| {
 2952        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2953        build_editor(buffer, window, cx)
 2954    });
 2955
 2956    _ = editor.update(cx, |editor, window, cx| {
 2957        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2958            s.select_display_ranges([
 2959                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2960                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2961                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2962            ])
 2963        });
 2964
 2965        editor.newline(&Newline, window, cx);
 2966        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2967    });
 2968}
 2969
 2970#[gpui::test]
 2971fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2972    init_test(cx, |_| {});
 2973
 2974    let editor = cx.add_window(|window, cx| {
 2975        let buffer = MultiBuffer::build_simple(
 2976            "
 2977                a
 2978                b(
 2979                    X
 2980                )
 2981                c(
 2982                    X
 2983                )
 2984            "
 2985            .unindent()
 2986            .as_str(),
 2987            cx,
 2988        );
 2989        let mut editor = build_editor(buffer, window, cx);
 2990        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2991            s.select_ranges([
 2992                Point::new(2, 4)..Point::new(2, 5),
 2993                Point::new(5, 4)..Point::new(5, 5),
 2994            ])
 2995        });
 2996        editor
 2997    });
 2998
 2999    _ = editor.update(cx, |editor, window, cx| {
 3000        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3001        editor.buffer.update(cx, |buffer, cx| {
 3002            buffer.edit(
 3003                [
 3004                    (Point::new(1, 2)..Point::new(3, 0), ""),
 3005                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3006                ],
 3007                None,
 3008                cx,
 3009            );
 3010            assert_eq!(
 3011                buffer.read(cx).text(),
 3012                "
 3013                    a
 3014                    b()
 3015                    c()
 3016                "
 3017                .unindent()
 3018            );
 3019        });
 3020        assert_eq!(
 3021            editor.selections.ranges(cx),
 3022            &[
 3023                Point::new(1, 2)..Point::new(1, 2),
 3024                Point::new(2, 2)..Point::new(2, 2),
 3025            ],
 3026        );
 3027
 3028        editor.newline(&Newline, window, cx);
 3029        assert_eq!(
 3030            editor.text(cx),
 3031            "
 3032                a
 3033                b(
 3034                )
 3035                c(
 3036                )
 3037            "
 3038            .unindent()
 3039        );
 3040
 3041        // The selections are moved after the inserted newlines
 3042        assert_eq!(
 3043            editor.selections.ranges(cx),
 3044            &[
 3045                Point::new(2, 0)..Point::new(2, 0),
 3046                Point::new(4, 0)..Point::new(4, 0),
 3047            ],
 3048        );
 3049    });
 3050}
 3051
 3052#[gpui::test]
 3053async fn test_newline_above(cx: &mut TestAppContext) {
 3054    init_test(cx, |settings| {
 3055        settings.defaults.tab_size = NonZeroU32::new(4)
 3056    });
 3057
 3058    let language = Arc::new(
 3059        Language::new(
 3060            LanguageConfig::default(),
 3061            Some(tree_sitter_rust::LANGUAGE.into()),
 3062        )
 3063        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3064        .unwrap(),
 3065    );
 3066
 3067    let mut cx = EditorTestContext::new(cx).await;
 3068    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3069    cx.set_state(indoc! {"
 3070        const a: ˇA = (
 3071 3072                «const_functionˇ»(ˇ),
 3073                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3074 3075        ˇ);ˇ
 3076    "});
 3077
 3078    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3079    cx.assert_editor_state(indoc! {"
 3080        ˇ
 3081        const a: A = (
 3082            ˇ
 3083            (
 3084                ˇ
 3085                ˇ
 3086                const_function(),
 3087                ˇ
 3088                ˇ
 3089                ˇ
 3090                ˇ
 3091                something_else,
 3092                ˇ
 3093            )
 3094            ˇ
 3095            ˇ
 3096        );
 3097    "});
 3098}
 3099
 3100#[gpui::test]
 3101async fn test_newline_below(cx: &mut TestAppContext) {
 3102    init_test(cx, |settings| {
 3103        settings.defaults.tab_size = NonZeroU32::new(4)
 3104    });
 3105
 3106    let language = Arc::new(
 3107        Language::new(
 3108            LanguageConfig::default(),
 3109            Some(tree_sitter_rust::LANGUAGE.into()),
 3110        )
 3111        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3112        .unwrap(),
 3113    );
 3114
 3115    let mut cx = EditorTestContext::new(cx).await;
 3116    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3117    cx.set_state(indoc! {"
 3118        const a: ˇA = (
 3119 3120                «const_functionˇ»(ˇ),
 3121                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3122 3123        ˇ);ˇ
 3124    "});
 3125
 3126    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3127    cx.assert_editor_state(indoc! {"
 3128        const a: A = (
 3129            ˇ
 3130            (
 3131                ˇ
 3132                const_function(),
 3133                ˇ
 3134                ˇ
 3135                something_else,
 3136                ˇ
 3137                ˇ
 3138                ˇ
 3139                ˇ
 3140            )
 3141            ˇ
 3142        );
 3143        ˇ
 3144        ˇ
 3145    "});
 3146}
 3147
 3148#[gpui::test]
 3149async fn test_newline_comments(cx: &mut TestAppContext) {
 3150    init_test(cx, |settings| {
 3151        settings.defaults.tab_size = NonZeroU32::new(4)
 3152    });
 3153
 3154    let language = Arc::new(Language::new(
 3155        LanguageConfig {
 3156            line_comments: vec!["// ".into()],
 3157            ..LanguageConfig::default()
 3158        },
 3159        None,
 3160    ));
 3161    {
 3162        let mut cx = EditorTestContext::new(cx).await;
 3163        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3164        cx.set_state(indoc! {"
 3165        // Fooˇ
 3166    "});
 3167
 3168        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3169        cx.assert_editor_state(indoc! {"
 3170        // Foo
 3171        // ˇ
 3172    "});
 3173        // Ensure that we add comment prefix when existing line contains space
 3174        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3175        cx.assert_editor_state(
 3176            indoc! {"
 3177        // Foo
 3178        //s
 3179        // ˇ
 3180    "}
 3181            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3182            .as_str(),
 3183        );
 3184        // Ensure that we add comment prefix when existing line does not contain space
 3185        cx.set_state(indoc! {"
 3186        // Foo
 3187        //ˇ
 3188    "});
 3189        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3190        cx.assert_editor_state(indoc! {"
 3191        // Foo
 3192        //
 3193        // ˇ
 3194    "});
 3195        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3196        cx.set_state(indoc! {"
 3197        ˇ// Foo
 3198    "});
 3199        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3200        cx.assert_editor_state(indoc! {"
 3201
 3202        ˇ// Foo
 3203    "});
 3204    }
 3205    // Ensure that comment continuations can be disabled.
 3206    update_test_language_settings(cx, |settings| {
 3207        settings.defaults.extend_comment_on_newline = Some(false);
 3208    });
 3209    let mut cx = EditorTestContext::new(cx).await;
 3210    cx.set_state(indoc! {"
 3211        // Fooˇ
 3212    "});
 3213    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3214    cx.assert_editor_state(indoc! {"
 3215        // Foo
 3216        ˇ
 3217    "});
 3218}
 3219
 3220#[gpui::test]
 3221async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3222    init_test(cx, |settings| {
 3223        settings.defaults.tab_size = NonZeroU32::new(4)
 3224    });
 3225
 3226    let language = Arc::new(Language::new(
 3227        LanguageConfig {
 3228            line_comments: vec!["// ".into(), "/// ".into()],
 3229            ..LanguageConfig::default()
 3230        },
 3231        None,
 3232    ));
 3233    {
 3234        let mut cx = EditorTestContext::new(cx).await;
 3235        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3236        cx.set_state(indoc! {"
 3237        //ˇ
 3238    "});
 3239        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3240        cx.assert_editor_state(indoc! {"
 3241        //
 3242        // ˇ
 3243    "});
 3244
 3245        cx.set_state(indoc! {"
 3246        ///ˇ
 3247    "});
 3248        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3249        cx.assert_editor_state(indoc! {"
 3250        ///
 3251        /// ˇ
 3252    "});
 3253    }
 3254}
 3255
 3256#[gpui::test]
 3257async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3258    init_test(cx, |settings| {
 3259        settings.defaults.tab_size = NonZeroU32::new(4)
 3260    });
 3261
 3262    let language = Arc::new(
 3263        Language::new(
 3264            LanguageConfig {
 3265                documentation_comment: Some(language::BlockCommentConfig {
 3266                    start: "/**".into(),
 3267                    end: "*/".into(),
 3268                    prefix: "* ".into(),
 3269                    tab_size: 1,
 3270                }),
 3271
 3272                ..LanguageConfig::default()
 3273            },
 3274            Some(tree_sitter_rust::LANGUAGE.into()),
 3275        )
 3276        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3277        .unwrap(),
 3278    );
 3279
 3280    {
 3281        let mut cx = EditorTestContext::new(cx).await;
 3282        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3283        cx.set_state(indoc! {"
 3284        /**ˇ
 3285    "});
 3286
 3287        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3288        cx.assert_editor_state(indoc! {"
 3289        /**
 3290         * ˇ
 3291    "});
 3292        // Ensure that if cursor is before the comment start,
 3293        // we do not actually insert a comment prefix.
 3294        cx.set_state(indoc! {"
 3295        ˇ/**
 3296    "});
 3297        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3298        cx.assert_editor_state(indoc! {"
 3299
 3300        ˇ/**
 3301    "});
 3302        // Ensure that if cursor is between it doesn't add comment prefix.
 3303        cx.set_state(indoc! {"
 3304        /*ˇ*
 3305    "});
 3306        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3307        cx.assert_editor_state(indoc! {"
 3308        /*
 3309        ˇ*
 3310    "});
 3311        // Ensure that if suffix exists on same line after cursor it adds new line.
 3312        cx.set_state(indoc! {"
 3313        /**ˇ*/
 3314    "});
 3315        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3316        cx.assert_editor_state(indoc! {"
 3317        /**
 3318         * ˇ
 3319         */
 3320    "});
 3321        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3322        cx.set_state(indoc! {"
 3323        /**ˇ */
 3324    "});
 3325        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3326        cx.assert_editor_state(indoc! {"
 3327        /**
 3328         * ˇ
 3329         */
 3330    "});
 3331        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3332        cx.set_state(indoc! {"
 3333        /** ˇ*/
 3334    "});
 3335        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3336        cx.assert_editor_state(
 3337            indoc! {"
 3338        /**s
 3339         * ˇ
 3340         */
 3341    "}
 3342            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3343            .as_str(),
 3344        );
 3345        // Ensure that delimiter space is preserved when newline on already
 3346        // spaced delimiter.
 3347        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3348        cx.assert_editor_state(
 3349            indoc! {"
 3350        /**s
 3351         *s
 3352         * ˇ
 3353         */
 3354    "}
 3355            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3356            .as_str(),
 3357        );
 3358        // Ensure that delimiter space is preserved when space is not
 3359        // on existing delimiter.
 3360        cx.set_state(indoc! {"
 3361        /**
 3362 3363         */
 3364    "});
 3365        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3366        cx.assert_editor_state(indoc! {"
 3367        /**
 3368         *
 3369         * ˇ
 3370         */
 3371    "});
 3372        // Ensure that if suffix exists on same line after cursor it
 3373        // doesn't add extra new line if prefix is not on same line.
 3374        cx.set_state(indoc! {"
 3375        /**
 3376        ˇ*/
 3377    "});
 3378        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3379        cx.assert_editor_state(indoc! {"
 3380        /**
 3381
 3382        ˇ*/
 3383    "});
 3384        // Ensure that it detects suffix after existing prefix.
 3385        cx.set_state(indoc! {"
 3386        /**ˇ/
 3387    "});
 3388        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3389        cx.assert_editor_state(indoc! {"
 3390        /**
 3391        ˇ/
 3392    "});
 3393        // Ensure that if suffix exists on same line before
 3394        // cursor it does not add comment prefix.
 3395        cx.set_state(indoc! {"
 3396        /** */ˇ
 3397    "});
 3398        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3399        cx.assert_editor_state(indoc! {"
 3400        /** */
 3401        ˇ
 3402    "});
 3403        // Ensure that if suffix exists on same line before
 3404        // cursor it does not add comment prefix.
 3405        cx.set_state(indoc! {"
 3406        /**
 3407         *
 3408         */ˇ
 3409    "});
 3410        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3411        cx.assert_editor_state(indoc! {"
 3412        /**
 3413         *
 3414         */
 3415         ˇ
 3416    "});
 3417
 3418        // Ensure that inline comment followed by code
 3419        // doesn't add comment prefix on newline
 3420        cx.set_state(indoc! {"
 3421        /** */ textˇ
 3422    "});
 3423        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3424        cx.assert_editor_state(indoc! {"
 3425        /** */ text
 3426        ˇ
 3427    "});
 3428
 3429        // Ensure that text after comment end tag
 3430        // doesn't add comment prefix on newline
 3431        cx.set_state(indoc! {"
 3432        /**
 3433         *
 3434         */ˇtext
 3435    "});
 3436        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3437        cx.assert_editor_state(indoc! {"
 3438        /**
 3439         *
 3440         */
 3441         ˇtext
 3442    "});
 3443
 3444        // Ensure if not comment block it doesn't
 3445        // add comment prefix on newline
 3446        cx.set_state(indoc! {"
 3447        * textˇ
 3448    "});
 3449        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3450        cx.assert_editor_state(indoc! {"
 3451        * text
 3452        ˇ
 3453    "});
 3454    }
 3455    // Ensure that comment continuations can be disabled.
 3456    update_test_language_settings(cx, |settings| {
 3457        settings.defaults.extend_comment_on_newline = Some(false);
 3458    });
 3459    let mut cx = EditorTestContext::new(cx).await;
 3460    cx.set_state(indoc! {"
 3461        /**ˇ
 3462    "});
 3463    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3464    cx.assert_editor_state(indoc! {"
 3465        /**
 3466        ˇ
 3467    "});
 3468}
 3469
 3470#[gpui::test]
 3471async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3472    init_test(cx, |settings| {
 3473        settings.defaults.tab_size = NonZeroU32::new(4)
 3474    });
 3475
 3476    let lua_language = Arc::new(Language::new(
 3477        LanguageConfig {
 3478            line_comments: vec!["--".into()],
 3479            block_comment: Some(language::BlockCommentConfig {
 3480                start: "--[[".into(),
 3481                prefix: "".into(),
 3482                end: "]]".into(),
 3483                tab_size: 0,
 3484            }),
 3485            ..LanguageConfig::default()
 3486        },
 3487        None,
 3488    ));
 3489
 3490    let mut cx = EditorTestContext::new(cx).await;
 3491    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3492
 3493    // Line with line comment should extend
 3494    cx.set_state(indoc! {"
 3495        --ˇ
 3496    "});
 3497    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3498    cx.assert_editor_state(indoc! {"
 3499        --
 3500        --ˇ
 3501    "});
 3502
 3503    // Line with block comment that matches line comment should not extend
 3504    cx.set_state(indoc! {"
 3505        --[[ˇ
 3506    "});
 3507    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3508    cx.assert_editor_state(indoc! {"
 3509        --[[
 3510        ˇ
 3511    "});
 3512}
 3513
 3514#[gpui::test]
 3515fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3516    init_test(cx, |_| {});
 3517
 3518    let editor = cx.add_window(|window, cx| {
 3519        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3520        let mut editor = build_editor(buffer, window, cx);
 3521        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3522            s.select_ranges([3..4, 11..12, 19..20])
 3523        });
 3524        editor
 3525    });
 3526
 3527    _ = editor.update(cx, |editor, window, cx| {
 3528        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3529        editor.buffer.update(cx, |buffer, cx| {
 3530            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3531            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3532        });
 3533        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3534
 3535        editor.insert("Z", window, cx);
 3536        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3537
 3538        // The selections are moved after the inserted characters
 3539        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3540    });
 3541}
 3542
 3543#[gpui::test]
 3544async fn test_tab(cx: &mut TestAppContext) {
 3545    init_test(cx, |settings| {
 3546        settings.defaults.tab_size = NonZeroU32::new(3)
 3547    });
 3548
 3549    let mut cx = EditorTestContext::new(cx).await;
 3550    cx.set_state(indoc! {"
 3551        ˇabˇc
 3552        ˇ🏀ˇ🏀ˇefg
 3553 3554    "});
 3555    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3556    cx.assert_editor_state(indoc! {"
 3557           ˇab ˇc
 3558           ˇ🏀  ˇ🏀  ˇefg
 3559        d  ˇ
 3560    "});
 3561
 3562    cx.set_state(indoc! {"
 3563        a
 3564        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3565    "});
 3566    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3567    cx.assert_editor_state(indoc! {"
 3568        a
 3569           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3570    "});
 3571}
 3572
 3573#[gpui::test]
 3574async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3575    init_test(cx, |_| {});
 3576
 3577    let mut cx = EditorTestContext::new(cx).await;
 3578    let language = Arc::new(
 3579        Language::new(
 3580            LanguageConfig::default(),
 3581            Some(tree_sitter_rust::LANGUAGE.into()),
 3582        )
 3583        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3584        .unwrap(),
 3585    );
 3586    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3587
 3588    // test when all cursors are not at suggested indent
 3589    // then simply move to their suggested indent location
 3590    cx.set_state(indoc! {"
 3591        const a: B = (
 3592            c(
 3593        ˇ
 3594        ˇ    )
 3595        );
 3596    "});
 3597    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3598    cx.assert_editor_state(indoc! {"
 3599        const a: B = (
 3600            c(
 3601                ˇ
 3602            ˇ)
 3603        );
 3604    "});
 3605
 3606    // test cursor already at suggested indent not moving when
 3607    // other cursors are yet to reach their suggested indents
 3608    cx.set_state(indoc! {"
 3609        ˇ
 3610        const a: B = (
 3611            c(
 3612                d(
 3613        ˇ
 3614                )
 3615        ˇ
 3616        ˇ    )
 3617        );
 3618    "});
 3619    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3620    cx.assert_editor_state(indoc! {"
 3621        ˇ
 3622        const a: B = (
 3623            c(
 3624                d(
 3625                    ˇ
 3626                )
 3627                ˇ
 3628            ˇ)
 3629        );
 3630    "});
 3631    // test when all cursors are at suggested indent then tab is inserted
 3632    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3633    cx.assert_editor_state(indoc! {"
 3634            ˇ
 3635        const a: B = (
 3636            c(
 3637                d(
 3638                        ˇ
 3639                )
 3640                    ˇ
 3641                ˇ)
 3642        );
 3643    "});
 3644
 3645    // test when current indent is less than suggested indent,
 3646    // we adjust line to match suggested indent and move cursor to it
 3647    //
 3648    // when no other cursor is at word boundary, all of them should move
 3649    cx.set_state(indoc! {"
 3650        const a: B = (
 3651            c(
 3652                d(
 3653        ˇ
 3654        ˇ   )
 3655        ˇ   )
 3656        );
 3657    "});
 3658    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3659    cx.assert_editor_state(indoc! {"
 3660        const a: B = (
 3661            c(
 3662                d(
 3663                    ˇ
 3664                ˇ)
 3665            ˇ)
 3666        );
 3667    "});
 3668
 3669    // test when current indent is less than suggested indent,
 3670    // we adjust line to match suggested indent and move cursor to it
 3671    //
 3672    // when some other cursor is at word boundary, it should not move
 3673    cx.set_state(indoc! {"
 3674        const a: B = (
 3675            c(
 3676                d(
 3677        ˇ
 3678        ˇ   )
 3679           ˇ)
 3680        );
 3681    "});
 3682    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3683    cx.assert_editor_state(indoc! {"
 3684        const a: B = (
 3685            c(
 3686                d(
 3687                    ˇ
 3688                ˇ)
 3689            ˇ)
 3690        );
 3691    "});
 3692
 3693    // test when current indent is more than suggested indent,
 3694    // we just move cursor to current indent instead of suggested indent
 3695    //
 3696    // when no other cursor is at word boundary, all of them should move
 3697    cx.set_state(indoc! {"
 3698        const a: B = (
 3699            c(
 3700                d(
 3701        ˇ
 3702        ˇ                )
 3703        ˇ   )
 3704        );
 3705    "});
 3706    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3707    cx.assert_editor_state(indoc! {"
 3708        const a: B = (
 3709            c(
 3710                d(
 3711                    ˇ
 3712                        ˇ)
 3713            ˇ)
 3714        );
 3715    "});
 3716    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3717    cx.assert_editor_state(indoc! {"
 3718        const a: B = (
 3719            c(
 3720                d(
 3721                        ˇ
 3722                            ˇ)
 3723                ˇ)
 3724        );
 3725    "});
 3726
 3727    // test when current indent is more than suggested indent,
 3728    // we just move cursor to current indent instead of suggested indent
 3729    //
 3730    // when some other cursor is at word boundary, it doesn't move
 3731    cx.set_state(indoc! {"
 3732        const a: B = (
 3733            c(
 3734                d(
 3735        ˇ
 3736        ˇ                )
 3737            ˇ)
 3738        );
 3739    "});
 3740    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3741    cx.assert_editor_state(indoc! {"
 3742        const a: B = (
 3743            c(
 3744                d(
 3745                    ˇ
 3746                        ˇ)
 3747            ˇ)
 3748        );
 3749    "});
 3750
 3751    // handle auto-indent when there are multiple cursors on the same line
 3752    cx.set_state(indoc! {"
 3753        const a: B = (
 3754            c(
 3755        ˇ    ˇ
 3756        ˇ    )
 3757        );
 3758    "});
 3759    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3760    cx.assert_editor_state(indoc! {"
 3761        const a: B = (
 3762            c(
 3763                ˇ
 3764            ˇ)
 3765        );
 3766    "});
 3767}
 3768
 3769#[gpui::test]
 3770async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3771    init_test(cx, |settings| {
 3772        settings.defaults.tab_size = NonZeroU32::new(3)
 3773    });
 3774
 3775    let mut cx = EditorTestContext::new(cx).await;
 3776    cx.set_state(indoc! {"
 3777         ˇ
 3778        \t ˇ
 3779        \t  ˇ
 3780        \t   ˇ
 3781         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3782    "});
 3783
 3784    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3785    cx.assert_editor_state(indoc! {"
 3786           ˇ
 3787        \t   ˇ
 3788        \t   ˇ
 3789        \t      ˇ
 3790         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3791    "});
 3792}
 3793
 3794#[gpui::test]
 3795async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3796    init_test(cx, |settings| {
 3797        settings.defaults.tab_size = NonZeroU32::new(4)
 3798    });
 3799
 3800    let language = Arc::new(
 3801        Language::new(
 3802            LanguageConfig::default(),
 3803            Some(tree_sitter_rust::LANGUAGE.into()),
 3804        )
 3805        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3806        .unwrap(),
 3807    );
 3808
 3809    let mut cx = EditorTestContext::new(cx).await;
 3810    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3811    cx.set_state(indoc! {"
 3812        fn a() {
 3813            if b {
 3814        \t ˇc
 3815            }
 3816        }
 3817    "});
 3818
 3819    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3820    cx.assert_editor_state(indoc! {"
 3821        fn a() {
 3822            if b {
 3823                ˇc
 3824            }
 3825        }
 3826    "});
 3827}
 3828
 3829#[gpui::test]
 3830async fn test_indent_outdent(cx: &mut TestAppContext) {
 3831    init_test(cx, |settings| {
 3832        settings.defaults.tab_size = NonZeroU32::new(4);
 3833    });
 3834
 3835    let mut cx = EditorTestContext::new(cx).await;
 3836
 3837    cx.set_state(indoc! {"
 3838          «oneˇ» «twoˇ»
 3839        three
 3840         four
 3841    "});
 3842    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3843    cx.assert_editor_state(indoc! {"
 3844            «oneˇ» «twoˇ»
 3845        three
 3846         four
 3847    "});
 3848
 3849    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3850    cx.assert_editor_state(indoc! {"
 3851        «oneˇ» «twoˇ»
 3852        three
 3853         four
 3854    "});
 3855
 3856    // select across line ending
 3857    cx.set_state(indoc! {"
 3858        one two
 3859        t«hree
 3860        ˇ» four
 3861    "});
 3862    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3863    cx.assert_editor_state(indoc! {"
 3864        one two
 3865            t«hree
 3866        ˇ» four
 3867    "});
 3868
 3869    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3870    cx.assert_editor_state(indoc! {"
 3871        one two
 3872        t«hree
 3873        ˇ» four
 3874    "});
 3875
 3876    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3877    cx.set_state(indoc! {"
 3878        one two
 3879        ˇthree
 3880            four
 3881    "});
 3882    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3883    cx.assert_editor_state(indoc! {"
 3884        one two
 3885            ˇthree
 3886            four
 3887    "});
 3888
 3889    cx.set_state(indoc! {"
 3890        one two
 3891        ˇ    three
 3892            four
 3893    "});
 3894    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3895    cx.assert_editor_state(indoc! {"
 3896        one two
 3897        ˇthree
 3898            four
 3899    "});
 3900}
 3901
 3902#[gpui::test]
 3903async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3904    // This is a regression test for issue #33761
 3905    init_test(cx, |_| {});
 3906
 3907    let mut cx = EditorTestContext::new(cx).await;
 3908    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3909    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3910
 3911    cx.set_state(
 3912        r#"ˇ#     ingress:
 3913ˇ#         api:
 3914ˇ#             enabled: false
 3915ˇ#             pathType: Prefix
 3916ˇ#           console:
 3917ˇ#               enabled: false
 3918ˇ#               pathType: Prefix
 3919"#,
 3920    );
 3921
 3922    // Press tab to indent all lines
 3923    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3924
 3925    cx.assert_editor_state(
 3926        r#"    ˇ#     ingress:
 3927    ˇ#         api:
 3928    ˇ#             enabled: false
 3929    ˇ#             pathType: Prefix
 3930    ˇ#           console:
 3931    ˇ#               enabled: false
 3932    ˇ#               pathType: Prefix
 3933"#,
 3934    );
 3935}
 3936
 3937#[gpui::test]
 3938async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3939    // This is a test to make sure our fix for issue #33761 didn't break anything
 3940    init_test(cx, |_| {});
 3941
 3942    let mut cx = EditorTestContext::new(cx).await;
 3943    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3944    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3945
 3946    cx.set_state(
 3947        r#"ˇingress:
 3948ˇ  api:
 3949ˇ    enabled: false
 3950ˇ    pathType: Prefix
 3951"#,
 3952    );
 3953
 3954    // Press tab to indent all lines
 3955    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3956
 3957    cx.assert_editor_state(
 3958        r#"ˇingress:
 3959    ˇapi:
 3960        ˇenabled: false
 3961        ˇpathType: Prefix
 3962"#,
 3963    );
 3964}
 3965
 3966#[gpui::test]
 3967async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3968    init_test(cx, |settings| {
 3969        settings.defaults.hard_tabs = Some(true);
 3970    });
 3971
 3972    let mut cx = EditorTestContext::new(cx).await;
 3973
 3974    // select two ranges on one line
 3975    cx.set_state(indoc! {"
 3976        «oneˇ» «twoˇ»
 3977        three
 3978        four
 3979    "});
 3980    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3981    cx.assert_editor_state(indoc! {"
 3982        \t«oneˇ» «twoˇ»
 3983        three
 3984        four
 3985    "});
 3986    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3987    cx.assert_editor_state(indoc! {"
 3988        \t\t«oneˇ» «twoˇ»
 3989        three
 3990        four
 3991    "});
 3992    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3993    cx.assert_editor_state(indoc! {"
 3994        \t«oneˇ» «twoˇ»
 3995        three
 3996        four
 3997    "});
 3998    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3999    cx.assert_editor_state(indoc! {"
 4000        «oneˇ» «twoˇ»
 4001        three
 4002        four
 4003    "});
 4004
 4005    // select across a line ending
 4006    cx.set_state(indoc! {"
 4007        one two
 4008        t«hree
 4009        ˇ»four
 4010    "});
 4011    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4012    cx.assert_editor_state(indoc! {"
 4013        one two
 4014        \tt«hree
 4015        ˇ»four
 4016    "});
 4017    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4018    cx.assert_editor_state(indoc! {"
 4019        one two
 4020        \t\tt«hree
 4021        ˇ»four
 4022    "});
 4023    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4024    cx.assert_editor_state(indoc! {"
 4025        one two
 4026        \tt«hree
 4027        ˇ»four
 4028    "});
 4029    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4030    cx.assert_editor_state(indoc! {"
 4031        one two
 4032        t«hree
 4033        ˇ»four
 4034    "});
 4035
 4036    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4037    cx.set_state(indoc! {"
 4038        one two
 4039        ˇthree
 4040        four
 4041    "});
 4042    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4043    cx.assert_editor_state(indoc! {"
 4044        one two
 4045        ˇthree
 4046        four
 4047    "});
 4048    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4049    cx.assert_editor_state(indoc! {"
 4050        one two
 4051        \tˇthree
 4052        four
 4053    "});
 4054    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4055    cx.assert_editor_state(indoc! {"
 4056        one two
 4057        ˇthree
 4058        four
 4059    "});
 4060}
 4061
 4062#[gpui::test]
 4063fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4064    init_test(cx, |settings| {
 4065        settings.languages.0.extend([
 4066            (
 4067                "TOML".into(),
 4068                LanguageSettingsContent {
 4069                    tab_size: NonZeroU32::new(2),
 4070                    ..Default::default()
 4071                },
 4072            ),
 4073            (
 4074                "Rust".into(),
 4075                LanguageSettingsContent {
 4076                    tab_size: NonZeroU32::new(4),
 4077                    ..Default::default()
 4078                },
 4079            ),
 4080        ]);
 4081    });
 4082
 4083    let toml_language = Arc::new(Language::new(
 4084        LanguageConfig {
 4085            name: "TOML".into(),
 4086            ..Default::default()
 4087        },
 4088        None,
 4089    ));
 4090    let rust_language = Arc::new(Language::new(
 4091        LanguageConfig {
 4092            name: "Rust".into(),
 4093            ..Default::default()
 4094        },
 4095        None,
 4096    ));
 4097
 4098    let toml_buffer =
 4099        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4100    let rust_buffer =
 4101        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4102    let multibuffer = cx.new(|cx| {
 4103        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4104        multibuffer.push_excerpts(
 4105            toml_buffer.clone(),
 4106            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4107            cx,
 4108        );
 4109        multibuffer.push_excerpts(
 4110            rust_buffer.clone(),
 4111            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4112            cx,
 4113        );
 4114        multibuffer
 4115    });
 4116
 4117    cx.add_window(|window, cx| {
 4118        let mut editor = build_editor(multibuffer, window, cx);
 4119
 4120        assert_eq!(
 4121            editor.text(cx),
 4122            indoc! {"
 4123                a = 1
 4124                b = 2
 4125
 4126                const c: usize = 3;
 4127            "}
 4128        );
 4129
 4130        select_ranges(
 4131            &mut editor,
 4132            indoc! {"
 4133                «aˇ» = 1
 4134                b = 2
 4135
 4136                «const c:ˇ» usize = 3;
 4137            "},
 4138            window,
 4139            cx,
 4140        );
 4141
 4142        editor.tab(&Tab, window, cx);
 4143        assert_text_with_selections(
 4144            &mut editor,
 4145            indoc! {"
 4146                  «aˇ» = 1
 4147                b = 2
 4148
 4149                    «const c:ˇ» usize = 3;
 4150            "},
 4151            cx,
 4152        );
 4153        editor.backtab(&Backtab, window, cx);
 4154        assert_text_with_selections(
 4155            &mut editor,
 4156            indoc! {"
 4157                «aˇ» = 1
 4158                b = 2
 4159
 4160                «const c:ˇ» usize = 3;
 4161            "},
 4162            cx,
 4163        );
 4164
 4165        editor
 4166    });
 4167}
 4168
 4169#[gpui::test]
 4170async fn test_backspace(cx: &mut TestAppContext) {
 4171    init_test(cx, |_| {});
 4172
 4173    let mut cx = EditorTestContext::new(cx).await;
 4174
 4175    // Basic backspace
 4176    cx.set_state(indoc! {"
 4177        onˇe two three
 4178        fou«rˇ» five six
 4179        seven «ˇeight nine
 4180        »ten
 4181    "});
 4182    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4183    cx.assert_editor_state(indoc! {"
 4184        oˇe two three
 4185        fouˇ five six
 4186        seven ˇten
 4187    "});
 4188
 4189    // Test backspace inside and around indents
 4190    cx.set_state(indoc! {"
 4191        zero
 4192            ˇone
 4193                ˇtwo
 4194            ˇ ˇ ˇ  three
 4195        ˇ  ˇ  four
 4196    "});
 4197    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4198    cx.assert_editor_state(indoc! {"
 4199        zero
 4200        ˇone
 4201            ˇtwo
 4202        ˇ  threeˇ  four
 4203    "});
 4204}
 4205
 4206#[gpui::test]
 4207async fn test_delete(cx: &mut TestAppContext) {
 4208    init_test(cx, |_| {});
 4209
 4210    let mut cx = EditorTestContext::new(cx).await;
 4211    cx.set_state(indoc! {"
 4212        onˇe two three
 4213        fou«rˇ» five six
 4214        seven «ˇeight nine
 4215        »ten
 4216    "});
 4217    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4218    cx.assert_editor_state(indoc! {"
 4219        onˇ two three
 4220        fouˇ five six
 4221        seven ˇten
 4222    "});
 4223}
 4224
 4225#[gpui::test]
 4226fn test_delete_line(cx: &mut TestAppContext) {
 4227    init_test(cx, |_| {});
 4228
 4229    let editor = cx.add_window(|window, cx| {
 4230        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4231        build_editor(buffer, window, cx)
 4232    });
 4233    _ = editor.update(cx, |editor, window, cx| {
 4234        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4235            s.select_display_ranges([
 4236                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4237                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4238                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4239            ])
 4240        });
 4241        editor.delete_line(&DeleteLine, window, cx);
 4242        assert_eq!(editor.display_text(cx), "ghi");
 4243        assert_eq!(
 4244            editor.selections.display_ranges(cx),
 4245            vec![
 4246                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4247                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 4248            ]
 4249        );
 4250    });
 4251
 4252    let editor = cx.add_window(|window, cx| {
 4253        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4254        build_editor(buffer, window, cx)
 4255    });
 4256    _ = editor.update(cx, |editor, window, cx| {
 4257        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4258            s.select_display_ranges([
 4259                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4260            ])
 4261        });
 4262        editor.delete_line(&DeleteLine, window, cx);
 4263        assert_eq!(editor.display_text(cx), "ghi\n");
 4264        assert_eq!(
 4265            editor.selections.display_ranges(cx),
 4266            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4267        );
 4268    });
 4269}
 4270
 4271#[gpui::test]
 4272fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4273    init_test(cx, |_| {});
 4274
 4275    cx.add_window(|window, cx| {
 4276        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4277        let mut editor = build_editor(buffer.clone(), window, cx);
 4278        let buffer = buffer.read(cx).as_singleton().unwrap();
 4279
 4280        assert_eq!(
 4281            editor.selections.ranges::<Point>(cx),
 4282            &[Point::new(0, 0)..Point::new(0, 0)]
 4283        );
 4284
 4285        // When on single line, replace newline at end by space
 4286        editor.join_lines(&JoinLines, window, cx);
 4287        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4288        assert_eq!(
 4289            editor.selections.ranges::<Point>(cx),
 4290            &[Point::new(0, 3)..Point::new(0, 3)]
 4291        );
 4292
 4293        // When multiple lines are selected, remove newlines that are spanned by the selection
 4294        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4295            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4296        });
 4297        editor.join_lines(&JoinLines, window, cx);
 4298        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4299        assert_eq!(
 4300            editor.selections.ranges::<Point>(cx),
 4301            &[Point::new(0, 11)..Point::new(0, 11)]
 4302        );
 4303
 4304        // Undo should be transactional
 4305        editor.undo(&Undo, window, cx);
 4306        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4307        assert_eq!(
 4308            editor.selections.ranges::<Point>(cx),
 4309            &[Point::new(0, 5)..Point::new(2, 2)]
 4310        );
 4311
 4312        // When joining an empty line don't insert a space
 4313        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4314            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4315        });
 4316        editor.join_lines(&JoinLines, window, cx);
 4317        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4318        assert_eq!(
 4319            editor.selections.ranges::<Point>(cx),
 4320            [Point::new(2, 3)..Point::new(2, 3)]
 4321        );
 4322
 4323        // We can remove trailing newlines
 4324        editor.join_lines(&JoinLines, window, cx);
 4325        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4326        assert_eq!(
 4327            editor.selections.ranges::<Point>(cx),
 4328            [Point::new(2, 3)..Point::new(2, 3)]
 4329        );
 4330
 4331        // We don't blow up on the last line
 4332        editor.join_lines(&JoinLines, window, cx);
 4333        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4334        assert_eq!(
 4335            editor.selections.ranges::<Point>(cx),
 4336            [Point::new(2, 3)..Point::new(2, 3)]
 4337        );
 4338
 4339        // reset to test indentation
 4340        editor.buffer.update(cx, |buffer, cx| {
 4341            buffer.edit(
 4342                [
 4343                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4344                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4345                ],
 4346                None,
 4347                cx,
 4348            )
 4349        });
 4350
 4351        // We remove any leading spaces
 4352        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4353        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4354            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4355        });
 4356        editor.join_lines(&JoinLines, window, cx);
 4357        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4358
 4359        // We don't insert a space for a line containing only spaces
 4360        editor.join_lines(&JoinLines, window, cx);
 4361        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4362
 4363        // We ignore any leading tabs
 4364        editor.join_lines(&JoinLines, window, cx);
 4365        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4366
 4367        editor
 4368    });
 4369}
 4370
 4371#[gpui::test]
 4372fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4373    init_test(cx, |_| {});
 4374
 4375    cx.add_window(|window, cx| {
 4376        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4377        let mut editor = build_editor(buffer.clone(), window, cx);
 4378        let buffer = buffer.read(cx).as_singleton().unwrap();
 4379
 4380        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4381            s.select_ranges([
 4382                Point::new(0, 2)..Point::new(1, 1),
 4383                Point::new(1, 2)..Point::new(1, 2),
 4384                Point::new(3, 1)..Point::new(3, 2),
 4385            ])
 4386        });
 4387
 4388        editor.join_lines(&JoinLines, window, cx);
 4389        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4390
 4391        assert_eq!(
 4392            editor.selections.ranges::<Point>(cx),
 4393            [
 4394                Point::new(0, 7)..Point::new(0, 7),
 4395                Point::new(1, 3)..Point::new(1, 3)
 4396            ]
 4397        );
 4398        editor
 4399    });
 4400}
 4401
 4402#[gpui::test]
 4403async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4404    init_test(cx, |_| {});
 4405
 4406    let mut cx = EditorTestContext::new(cx).await;
 4407
 4408    let diff_base = r#"
 4409        Line 0
 4410        Line 1
 4411        Line 2
 4412        Line 3
 4413        "#
 4414    .unindent();
 4415
 4416    cx.set_state(
 4417        &r#"
 4418        ˇLine 0
 4419        Line 1
 4420        Line 2
 4421        Line 3
 4422        "#
 4423        .unindent(),
 4424    );
 4425
 4426    cx.set_head_text(&diff_base);
 4427    executor.run_until_parked();
 4428
 4429    // Join lines
 4430    cx.update_editor(|editor, window, cx| {
 4431        editor.join_lines(&JoinLines, window, cx);
 4432    });
 4433    executor.run_until_parked();
 4434
 4435    cx.assert_editor_state(
 4436        &r#"
 4437        Line 0ˇ Line 1
 4438        Line 2
 4439        Line 3
 4440        "#
 4441        .unindent(),
 4442    );
 4443    // Join again
 4444    cx.update_editor(|editor, window, cx| {
 4445        editor.join_lines(&JoinLines, window, cx);
 4446    });
 4447    executor.run_until_parked();
 4448
 4449    cx.assert_editor_state(
 4450        &r#"
 4451        Line 0 Line 1ˇ Line 2
 4452        Line 3
 4453        "#
 4454        .unindent(),
 4455    );
 4456}
 4457
 4458#[gpui::test]
 4459async fn test_custom_newlines_cause_no_false_positive_diffs(
 4460    executor: BackgroundExecutor,
 4461    cx: &mut TestAppContext,
 4462) {
 4463    init_test(cx, |_| {});
 4464    let mut cx = EditorTestContext::new(cx).await;
 4465    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4466    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4467    executor.run_until_parked();
 4468
 4469    cx.update_editor(|editor, window, cx| {
 4470        let snapshot = editor.snapshot(window, cx);
 4471        assert_eq!(
 4472            snapshot
 4473                .buffer_snapshot
 4474                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 4475                .collect::<Vec<_>>(),
 4476            Vec::new(),
 4477            "Should not have any diffs for files with custom newlines"
 4478        );
 4479    });
 4480}
 4481
 4482#[gpui::test]
 4483async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4484    init_test(cx, |_| {});
 4485
 4486    let mut cx = EditorTestContext::new(cx).await;
 4487
 4488    // Test sort_lines_case_insensitive()
 4489    cx.set_state(indoc! {"
 4490        «z
 4491        y
 4492        x
 4493        Z
 4494        Y
 4495        Xˇ»
 4496    "});
 4497    cx.update_editor(|e, window, cx| {
 4498        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4499    });
 4500    cx.assert_editor_state(indoc! {"
 4501        «x
 4502        X
 4503        y
 4504        Y
 4505        z
 4506        Zˇ»
 4507    "});
 4508
 4509    // Test sort_lines_by_length()
 4510    //
 4511    // Demonstrates:
 4512    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4513    // - sort is stable
 4514    cx.set_state(indoc! {"
 4515        «123
 4516        æ
 4517        12
 4518 4519        1
 4520        æˇ»
 4521    "});
 4522    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4523    cx.assert_editor_state(indoc! {"
 4524        «æ
 4525 4526        1
 4527        æ
 4528        12
 4529        123ˇ»
 4530    "});
 4531
 4532    // Test reverse_lines()
 4533    cx.set_state(indoc! {"
 4534        «5
 4535        4
 4536        3
 4537        2
 4538        1ˇ»
 4539    "});
 4540    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4541    cx.assert_editor_state(indoc! {"
 4542        «1
 4543        2
 4544        3
 4545        4
 4546        5ˇ»
 4547    "});
 4548
 4549    // Skip testing shuffle_line()
 4550
 4551    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4552    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4553
 4554    // Don't manipulate when cursor is on single line, but expand the selection
 4555    cx.set_state(indoc! {"
 4556        ddˇdd
 4557        ccc
 4558        bb
 4559        a
 4560    "});
 4561    cx.update_editor(|e, window, cx| {
 4562        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4563    });
 4564    cx.assert_editor_state(indoc! {"
 4565        «ddddˇ»
 4566        ccc
 4567        bb
 4568        a
 4569    "});
 4570
 4571    // Basic manipulate case
 4572    // Start selection moves to column 0
 4573    // End of selection shrinks to fit shorter line
 4574    cx.set_state(indoc! {"
 4575        dd«d
 4576        ccc
 4577        bb
 4578        aaaaaˇ»
 4579    "});
 4580    cx.update_editor(|e, window, cx| {
 4581        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4582    });
 4583    cx.assert_editor_state(indoc! {"
 4584        «aaaaa
 4585        bb
 4586        ccc
 4587        dddˇ»
 4588    "});
 4589
 4590    // Manipulate case with newlines
 4591    cx.set_state(indoc! {"
 4592        dd«d
 4593        ccc
 4594
 4595        bb
 4596        aaaaa
 4597
 4598        ˇ»
 4599    "});
 4600    cx.update_editor(|e, window, cx| {
 4601        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4602    });
 4603    cx.assert_editor_state(indoc! {"
 4604        «
 4605
 4606        aaaaa
 4607        bb
 4608        ccc
 4609        dddˇ»
 4610
 4611    "});
 4612
 4613    // Adding new line
 4614    cx.set_state(indoc! {"
 4615        aa«a
 4616        bbˇ»b
 4617    "});
 4618    cx.update_editor(|e, window, cx| {
 4619        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4620    });
 4621    cx.assert_editor_state(indoc! {"
 4622        «aaa
 4623        bbb
 4624        added_lineˇ»
 4625    "});
 4626
 4627    // Removing line
 4628    cx.set_state(indoc! {"
 4629        aa«a
 4630        bbbˇ»
 4631    "});
 4632    cx.update_editor(|e, window, cx| {
 4633        e.manipulate_immutable_lines(window, cx, |lines| {
 4634            lines.pop();
 4635        })
 4636    });
 4637    cx.assert_editor_state(indoc! {"
 4638        «aaaˇ»
 4639    "});
 4640
 4641    // Removing all lines
 4642    cx.set_state(indoc! {"
 4643        aa«a
 4644        bbbˇ»
 4645    "});
 4646    cx.update_editor(|e, window, cx| {
 4647        e.manipulate_immutable_lines(window, cx, |lines| {
 4648            lines.drain(..);
 4649        })
 4650    });
 4651    cx.assert_editor_state(indoc! {"
 4652        ˇ
 4653    "});
 4654}
 4655
 4656#[gpui::test]
 4657async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4658    init_test(cx, |_| {});
 4659
 4660    let mut cx = EditorTestContext::new(cx).await;
 4661
 4662    // Consider continuous selection as single selection
 4663    cx.set_state(indoc! {"
 4664        Aaa«aa
 4665        cˇ»c«c
 4666        bb
 4667        aaaˇ»aa
 4668    "});
 4669    cx.update_editor(|e, window, cx| {
 4670        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4671    });
 4672    cx.assert_editor_state(indoc! {"
 4673        «Aaaaa
 4674        ccc
 4675        bb
 4676        aaaaaˇ»
 4677    "});
 4678
 4679    cx.set_state(indoc! {"
 4680        Aaa«aa
 4681        cˇ»c«c
 4682        bb
 4683        aaaˇ»aa
 4684    "});
 4685    cx.update_editor(|e, window, cx| {
 4686        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4687    });
 4688    cx.assert_editor_state(indoc! {"
 4689        «Aaaaa
 4690        ccc
 4691        bbˇ»
 4692    "});
 4693
 4694    // Consider non continuous selection as distinct dedup operations
 4695    cx.set_state(indoc! {"
 4696        «aaaaa
 4697        bb
 4698        aaaaa
 4699        aaaaaˇ»
 4700
 4701        aaa«aaˇ»
 4702    "});
 4703    cx.update_editor(|e, window, cx| {
 4704        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4705    });
 4706    cx.assert_editor_state(indoc! {"
 4707        «aaaaa
 4708        bbˇ»
 4709
 4710        «aaaaaˇ»
 4711    "});
 4712}
 4713
 4714#[gpui::test]
 4715async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4716    init_test(cx, |_| {});
 4717
 4718    let mut cx = EditorTestContext::new(cx).await;
 4719
 4720    cx.set_state(indoc! {"
 4721        «Aaa
 4722        aAa
 4723        Aaaˇ»
 4724    "});
 4725    cx.update_editor(|e, window, cx| {
 4726        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4727    });
 4728    cx.assert_editor_state(indoc! {"
 4729        «Aaa
 4730        aAaˇ»
 4731    "});
 4732
 4733    cx.set_state(indoc! {"
 4734        «Aaa
 4735        aAa
 4736        aaAˇ»
 4737    "});
 4738    cx.update_editor(|e, window, cx| {
 4739        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4740    });
 4741    cx.assert_editor_state(indoc! {"
 4742        «Aaaˇ»
 4743    "});
 4744}
 4745
 4746#[gpui::test]
 4747async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 4748    init_test(cx, |_| {});
 4749
 4750    let mut cx = EditorTestContext::new(cx).await;
 4751
 4752    let js_language = Arc::new(Language::new(
 4753        LanguageConfig {
 4754            name: "JavaScript".into(),
 4755            wrap_characters: Some(language::WrapCharactersConfig {
 4756                start_prefix: "<".into(),
 4757                start_suffix: ">".into(),
 4758                end_prefix: "</".into(),
 4759                end_suffix: ">".into(),
 4760            }),
 4761            ..LanguageConfig::default()
 4762        },
 4763        None,
 4764    ));
 4765
 4766    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4767
 4768    cx.set_state(indoc! {"
 4769        «testˇ»
 4770    "});
 4771    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4772    cx.assert_editor_state(indoc! {"
 4773        <«ˇ»>test</«ˇ»>
 4774    "});
 4775
 4776    cx.set_state(indoc! {"
 4777        «test
 4778         testˇ»
 4779    "});
 4780    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4781    cx.assert_editor_state(indoc! {"
 4782        <«ˇ»>test
 4783         test</«ˇ»>
 4784    "});
 4785
 4786    cx.set_state(indoc! {"
 4787        teˇst
 4788    "});
 4789    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4790    cx.assert_editor_state(indoc! {"
 4791        te<«ˇ»></«ˇ»>st
 4792    "});
 4793}
 4794
 4795#[gpui::test]
 4796async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 4797    init_test(cx, |_| {});
 4798
 4799    let mut cx = EditorTestContext::new(cx).await;
 4800
 4801    let js_language = Arc::new(Language::new(
 4802        LanguageConfig {
 4803            name: "JavaScript".into(),
 4804            wrap_characters: Some(language::WrapCharactersConfig {
 4805                start_prefix: "<".into(),
 4806                start_suffix: ">".into(),
 4807                end_prefix: "</".into(),
 4808                end_suffix: ">".into(),
 4809            }),
 4810            ..LanguageConfig::default()
 4811        },
 4812        None,
 4813    ));
 4814
 4815    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4816
 4817    cx.set_state(indoc! {"
 4818        «testˇ»
 4819        «testˇ» «testˇ»
 4820        «testˇ»
 4821    "});
 4822    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4823    cx.assert_editor_state(indoc! {"
 4824        <«ˇ»>test</«ˇ»>
 4825        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 4826        <«ˇ»>test</«ˇ»>
 4827    "});
 4828
 4829    cx.set_state(indoc! {"
 4830        «test
 4831         testˇ»
 4832        «test
 4833         testˇ»
 4834    "});
 4835    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4836    cx.assert_editor_state(indoc! {"
 4837        <«ˇ»>test
 4838         test</«ˇ»>
 4839        <«ˇ»>test
 4840         test</«ˇ»>
 4841    "});
 4842}
 4843
 4844#[gpui::test]
 4845async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 4846    init_test(cx, |_| {});
 4847
 4848    let mut cx = EditorTestContext::new(cx).await;
 4849
 4850    let plaintext_language = Arc::new(Language::new(
 4851        LanguageConfig {
 4852            name: "Plain Text".into(),
 4853            ..LanguageConfig::default()
 4854        },
 4855        None,
 4856    ));
 4857
 4858    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 4859
 4860    cx.set_state(indoc! {"
 4861        «testˇ»
 4862    "});
 4863    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4864    cx.assert_editor_state(indoc! {"
 4865      «testˇ»
 4866    "});
 4867}
 4868
 4869#[gpui::test]
 4870async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4871    init_test(cx, |_| {});
 4872
 4873    let mut cx = EditorTestContext::new(cx).await;
 4874
 4875    // Manipulate with multiple selections on a single line
 4876    cx.set_state(indoc! {"
 4877        dd«dd
 4878        cˇ»c«c
 4879        bb
 4880        aaaˇ»aa
 4881    "});
 4882    cx.update_editor(|e, window, cx| {
 4883        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4884    });
 4885    cx.assert_editor_state(indoc! {"
 4886        «aaaaa
 4887        bb
 4888        ccc
 4889        ddddˇ»
 4890    "});
 4891
 4892    // Manipulate with multiple disjoin selections
 4893    cx.set_state(indoc! {"
 4894 4895        4
 4896        3
 4897        2
 4898        1ˇ»
 4899
 4900        dd«dd
 4901        ccc
 4902        bb
 4903        aaaˇ»aa
 4904    "});
 4905    cx.update_editor(|e, window, cx| {
 4906        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4907    });
 4908    cx.assert_editor_state(indoc! {"
 4909        «1
 4910        2
 4911        3
 4912        4
 4913        5ˇ»
 4914
 4915        «aaaaa
 4916        bb
 4917        ccc
 4918        ddddˇ»
 4919    "});
 4920
 4921    // Adding lines on each selection
 4922    cx.set_state(indoc! {"
 4923 4924        1ˇ»
 4925
 4926        bb«bb
 4927        aaaˇ»aa
 4928    "});
 4929    cx.update_editor(|e, window, cx| {
 4930        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4931    });
 4932    cx.assert_editor_state(indoc! {"
 4933        «2
 4934        1
 4935        added lineˇ»
 4936
 4937        «bbbb
 4938        aaaaa
 4939        added lineˇ»
 4940    "});
 4941
 4942    // Removing lines on each selection
 4943    cx.set_state(indoc! {"
 4944 4945        1ˇ»
 4946
 4947        bb«bb
 4948        aaaˇ»aa
 4949    "});
 4950    cx.update_editor(|e, window, cx| {
 4951        e.manipulate_immutable_lines(window, cx, |lines| {
 4952            lines.pop();
 4953        })
 4954    });
 4955    cx.assert_editor_state(indoc! {"
 4956        «2ˇ»
 4957
 4958        «bbbbˇ»
 4959    "});
 4960}
 4961
 4962#[gpui::test]
 4963async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 4964    init_test(cx, |settings| {
 4965        settings.defaults.tab_size = NonZeroU32::new(3)
 4966    });
 4967
 4968    let mut cx = EditorTestContext::new(cx).await;
 4969
 4970    // MULTI SELECTION
 4971    // Ln.1 "«" tests empty lines
 4972    // Ln.9 tests just leading whitespace
 4973    cx.set_state(indoc! {"
 4974        «
 4975        abc                 // No indentationˇ»
 4976        «\tabc              // 1 tabˇ»
 4977        \t\tabc «      ˇ»   // 2 tabs
 4978        \t ab«c             // Tab followed by space
 4979         \tabc              // Space followed by tab (3 spaces should be the result)
 4980        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4981           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 4982        \t
 4983        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4984    "});
 4985    cx.update_editor(|e, window, cx| {
 4986        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4987    });
 4988    cx.assert_editor_state(
 4989        indoc! {"
 4990            «
 4991            abc                 // No indentation
 4992               abc              // 1 tab
 4993                  abc          // 2 tabs
 4994                abc             // Tab followed by space
 4995               abc              // Space followed by tab (3 spaces should be the result)
 4996                           abc   // Mixed indentation (tab conversion depends on the column)
 4997               abc         // Already space indented
 4998               ·
 4999               abc\tdef          // Only the leading tab is manipulatedˇ»
 5000        "}
 5001        .replace("·", "")
 5002        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5003    );
 5004
 5005    // Test on just a few lines, the others should remain unchanged
 5006    // Only lines (3, 5, 10, 11) should change
 5007    cx.set_state(
 5008        indoc! {"
 5009            ·
 5010            abc                 // No indentation
 5011            \tabcˇ               // 1 tab
 5012            \t\tabc             // 2 tabs
 5013            \t abcˇ              // Tab followed by space
 5014             \tabc              // Space followed by tab (3 spaces should be the result)
 5015            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5016               abc              // Already space indented
 5017            «\t
 5018            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5019        "}
 5020        .replace("·", "")
 5021        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5022    );
 5023    cx.update_editor(|e, window, cx| {
 5024        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5025    });
 5026    cx.assert_editor_state(
 5027        indoc! {"
 5028            ·
 5029            abc                 // No indentation
 5030            «   abc               // 1 tabˇ»
 5031            \t\tabc             // 2 tabs
 5032            «    abc              // Tab followed by spaceˇ»
 5033             \tabc              // Space followed by tab (3 spaces should be the result)
 5034            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5035               abc              // Already space indented
 5036            «   ·
 5037               abc\tdef          // Only the leading tab is manipulatedˇ»
 5038        "}
 5039        .replace("·", "")
 5040        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5041    );
 5042
 5043    // SINGLE SELECTION
 5044    // Ln.1 "«" tests empty lines
 5045    // Ln.9 tests just leading whitespace
 5046    cx.set_state(indoc! {"
 5047        «
 5048        abc                 // No indentation
 5049        \tabc               // 1 tab
 5050        \t\tabc             // 2 tabs
 5051        \t abc              // Tab followed by space
 5052         \tabc              // Space followed by tab (3 spaces should be the result)
 5053        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5054           abc              // Already space indented
 5055        \t
 5056        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5057    "});
 5058    cx.update_editor(|e, window, cx| {
 5059        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5060    });
 5061    cx.assert_editor_state(
 5062        indoc! {"
 5063            «
 5064            abc                 // No indentation
 5065               abc               // 1 tab
 5066                  abc             // 2 tabs
 5067                abc              // Tab followed by space
 5068               abc              // Space followed by tab (3 spaces should be the result)
 5069                           abc   // Mixed indentation (tab conversion depends on the column)
 5070               abc              // Already space indented
 5071               ·
 5072               abc\tdef          // Only the leading tab is manipulatedˇ»
 5073        "}
 5074        .replace("·", "")
 5075        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5076    );
 5077}
 5078
 5079#[gpui::test]
 5080async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5081    init_test(cx, |settings| {
 5082        settings.defaults.tab_size = NonZeroU32::new(3)
 5083    });
 5084
 5085    let mut cx = EditorTestContext::new(cx).await;
 5086
 5087    // MULTI SELECTION
 5088    // Ln.1 "«" tests empty lines
 5089    // Ln.11 tests just leading whitespace
 5090    cx.set_state(indoc! {"
 5091        «
 5092        abˇ»ˇc                 // No indentation
 5093         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5094          abc  «             // 2 spaces (< 3 so dont convert)
 5095           abc              // 3 spaces (convert)
 5096             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5097        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5098        «\t abc              // Tab followed by space
 5099         \tabc              // Space followed by tab (should be consumed due to tab)
 5100        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5101           \tˇ»  «\t
 5102           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5103    "});
 5104    cx.update_editor(|e, window, cx| {
 5105        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5106    });
 5107    cx.assert_editor_state(indoc! {"
 5108        «
 5109        abc                 // No indentation
 5110         abc                // 1 space (< 3 so dont convert)
 5111          abc               // 2 spaces (< 3 so dont convert)
 5112        \tabc              // 3 spaces (convert)
 5113        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5114        \t\t\tabc           // Already tab indented
 5115        \t abc              // Tab followed by space
 5116        \tabc              // Space followed by tab (should be consumed due to tab)
 5117        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5118        \t\t\t
 5119        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5120    "});
 5121
 5122    // Test on just a few lines, the other should remain unchanged
 5123    // Only lines (4, 8, 11, 12) should change
 5124    cx.set_state(
 5125        indoc! {"
 5126            ·
 5127            abc                 // No indentation
 5128             abc                // 1 space (< 3 so dont convert)
 5129              abc               // 2 spaces (< 3 so dont convert)
 5130            «   abc              // 3 spaces (convert)ˇ»
 5131                 abc            // 5 spaces (1 tab + 2 spaces)
 5132            \t\t\tabc           // Already tab indented
 5133            \t abc              // Tab followed by space
 5134             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5135               \t\t  \tabc      // Mixed indentation
 5136            \t \t  \t   \tabc   // Mixed indentation
 5137               \t  \tˇ
 5138            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5139        "}
 5140        .replace("·", "")
 5141        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5142    );
 5143    cx.update_editor(|e, window, cx| {
 5144        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5145    });
 5146    cx.assert_editor_state(
 5147        indoc! {"
 5148            ·
 5149            abc                 // No indentation
 5150             abc                // 1 space (< 3 so dont convert)
 5151              abc               // 2 spaces (< 3 so dont convert)
 5152            «\tabc              // 3 spaces (convert)ˇ»
 5153                 abc            // 5 spaces (1 tab + 2 spaces)
 5154            \t\t\tabc           // 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  \tabc      // Mixed indentation
 5158            \t \t  \t   \tabc   // Mixed indentation
 5159            «\t\t\t
 5160            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5161        "}
 5162        .replace("·", "")
 5163        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5164    );
 5165
 5166    // SINGLE SELECTION
 5167    // Ln.1 "«" tests empty lines
 5168    // Ln.11 tests just leading whitespace
 5169    cx.set_state(indoc! {"
 5170        «
 5171        abc                 // No indentation
 5172         abc                // 1 space (< 3 so dont convert)
 5173          abc               // 2 spaces (< 3 so dont convert)
 5174           abc              // 3 spaces (convert)
 5175             abc            // 5 spaces (1 tab + 2 spaces)
 5176        \t\t\tabc           // Already tab indented
 5177        \t abc              // Tab followed by space
 5178         \tabc              // Space followed by tab (should be consumed due to tab)
 5179        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5180           \t  \t
 5181           abc   \t         // Only the leading spaces should be convertedˇ»
 5182    "});
 5183    cx.update_editor(|e, window, cx| {
 5184        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5185    });
 5186    cx.assert_editor_state(indoc! {"
 5187        «
 5188        abc                 // No indentation
 5189         abc                // 1 space (< 3 so dont convert)
 5190          abc               // 2 spaces (< 3 so dont convert)
 5191        \tabc              // 3 spaces (convert)
 5192        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5193        \t\t\tabc           // Already tab indented
 5194        \t abc              // Tab followed by space
 5195        \tabc              // Space followed by tab (should be consumed due to tab)
 5196        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5197        \t\t\t
 5198        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5199    "});
 5200}
 5201
 5202#[gpui::test]
 5203async fn test_toggle_case(cx: &mut TestAppContext) {
 5204    init_test(cx, |_| {});
 5205
 5206    let mut cx = EditorTestContext::new(cx).await;
 5207
 5208    // If all lower case -> upper case
 5209    cx.set_state(indoc! {"
 5210        «hello worldˇ»
 5211    "});
 5212    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5213    cx.assert_editor_state(indoc! {"
 5214        «HELLO WORLDˇ»
 5215    "});
 5216
 5217    // If all upper case -> lower case
 5218    cx.set_state(indoc! {"
 5219        «HELLO WORLDˇ»
 5220    "});
 5221    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5222    cx.assert_editor_state(indoc! {"
 5223        «hello worldˇ»
 5224    "});
 5225
 5226    // If any upper case characters are identified -> lower case
 5227    // This matches JetBrains IDEs
 5228    cx.set_state(indoc! {"
 5229        «hEllo worldˇ»
 5230    "});
 5231    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5232    cx.assert_editor_state(indoc! {"
 5233        «hello worldˇ»
 5234    "});
 5235}
 5236
 5237#[gpui::test]
 5238async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5239    init_test(cx, |_| {});
 5240
 5241    let mut cx = EditorTestContext::new(cx).await;
 5242
 5243    cx.set_state(indoc! {"
 5244        «implement-windows-supportˇ»
 5245    "});
 5246    cx.update_editor(|e, window, cx| {
 5247        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5248    });
 5249    cx.assert_editor_state(indoc! {"
 5250        «Implement windows supportˇ»
 5251    "});
 5252}
 5253
 5254#[gpui::test]
 5255async fn test_manipulate_text(cx: &mut TestAppContext) {
 5256    init_test(cx, |_| {});
 5257
 5258    let mut cx = EditorTestContext::new(cx).await;
 5259
 5260    // Test convert_to_upper_case()
 5261    cx.set_state(indoc! {"
 5262        «hello worldˇ»
 5263    "});
 5264    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5265    cx.assert_editor_state(indoc! {"
 5266        «HELLO WORLDˇ»
 5267    "});
 5268
 5269    // Test convert_to_lower_case()
 5270    cx.set_state(indoc! {"
 5271        «HELLO WORLDˇ»
 5272    "});
 5273    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5274    cx.assert_editor_state(indoc! {"
 5275        «hello worldˇ»
 5276    "});
 5277
 5278    // Test multiple line, single selection case
 5279    cx.set_state(indoc! {"
 5280        «The quick brown
 5281        fox jumps over
 5282        the lazy dogˇ»
 5283    "});
 5284    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5285    cx.assert_editor_state(indoc! {"
 5286        «The Quick Brown
 5287        Fox Jumps Over
 5288        The Lazy Dogˇ»
 5289    "});
 5290
 5291    // Test multiple line, single selection case
 5292    cx.set_state(indoc! {"
 5293        «The quick brown
 5294        fox jumps over
 5295        the lazy dogˇ»
 5296    "});
 5297    cx.update_editor(|e, window, cx| {
 5298        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5299    });
 5300    cx.assert_editor_state(indoc! {"
 5301        «TheQuickBrown
 5302        FoxJumpsOver
 5303        TheLazyDogˇ»
 5304    "});
 5305
 5306    // From here on out, test more complex cases of manipulate_text()
 5307
 5308    // Test no selection case - should affect words cursors are in
 5309    // Cursor at beginning, middle, and end of word
 5310    cx.set_state(indoc! {"
 5311        ˇhello big beauˇtiful worldˇ
 5312    "});
 5313    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5314    cx.assert_editor_state(indoc! {"
 5315        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5316    "});
 5317
 5318    // Test multiple selections on a single line and across multiple lines
 5319    cx.set_state(indoc! {"
 5320        «Theˇ» quick «brown
 5321        foxˇ» jumps «overˇ»
 5322        the «lazyˇ» dog
 5323    "});
 5324    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5325    cx.assert_editor_state(indoc! {"
 5326        «THEˇ» quick «BROWN
 5327        FOXˇ» jumps «OVERˇ»
 5328        the «LAZYˇ» dog
 5329    "});
 5330
 5331    // Test case where text length grows
 5332    cx.set_state(indoc! {"
 5333        «tschüߡ»
 5334    "});
 5335    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5336    cx.assert_editor_state(indoc! {"
 5337        «TSCHÜSSˇ»
 5338    "});
 5339
 5340    // Test to make sure we don't crash when text shrinks
 5341    cx.set_state(indoc! {"
 5342        aaa_bbbˇ
 5343    "});
 5344    cx.update_editor(|e, window, cx| {
 5345        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5346    });
 5347    cx.assert_editor_state(indoc! {"
 5348        «aaaBbbˇ»
 5349    "});
 5350
 5351    // Test to make sure we all aware of the fact that each word can grow and shrink
 5352    // Final selections should be aware of this fact
 5353    cx.set_state(indoc! {"
 5354        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5355    "});
 5356    cx.update_editor(|e, window, cx| {
 5357        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5358    });
 5359    cx.assert_editor_state(indoc! {"
 5360        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5361    "});
 5362
 5363    cx.set_state(indoc! {"
 5364        «hElLo, WoRld!ˇ»
 5365    "});
 5366    cx.update_editor(|e, window, cx| {
 5367        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5368    });
 5369    cx.assert_editor_state(indoc! {"
 5370        «HeLlO, wOrLD!ˇ»
 5371    "});
 5372
 5373    // Test selections with `line_mode() = true`.
 5374    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 5375    cx.set_state(indoc! {"
 5376        «The quick brown
 5377        fox jumps over
 5378        tˇ»he lazy dog
 5379    "});
 5380    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5381    cx.assert_editor_state(indoc! {"
 5382        «THE QUICK BROWN
 5383        FOX JUMPS OVER
 5384        THE LAZY DOGˇ»
 5385    "});
 5386}
 5387
 5388#[gpui::test]
 5389fn test_duplicate_line(cx: &mut TestAppContext) {
 5390    init_test(cx, |_| {});
 5391
 5392    let editor = cx.add_window(|window, cx| {
 5393        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5394        build_editor(buffer, window, cx)
 5395    });
 5396    _ = editor.update(cx, |editor, window, cx| {
 5397        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5398            s.select_display_ranges([
 5399                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5400                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5401                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5402                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5403            ])
 5404        });
 5405        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5406        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5407        assert_eq!(
 5408            editor.selections.display_ranges(cx),
 5409            vec![
 5410                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5411                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5412                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5413                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5414            ]
 5415        );
 5416    });
 5417
 5418    let editor = cx.add_window(|window, cx| {
 5419        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5420        build_editor(buffer, window, cx)
 5421    });
 5422    _ = editor.update(cx, |editor, window, cx| {
 5423        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5424            s.select_display_ranges([
 5425                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5426                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5427            ])
 5428        });
 5429        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5430        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5431        assert_eq!(
 5432            editor.selections.display_ranges(cx),
 5433            vec![
 5434                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5435                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5436            ]
 5437        );
 5438    });
 5439
 5440    // With `move_upwards` the selections stay in place, except for
 5441    // the lines inserted above them
 5442    let editor = cx.add_window(|window, cx| {
 5443        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5444        build_editor(buffer, window, cx)
 5445    });
 5446    _ = editor.update(cx, |editor, window, cx| {
 5447        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5448            s.select_display_ranges([
 5449                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5450                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5451                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5452                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5453            ])
 5454        });
 5455        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5456        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5457        assert_eq!(
 5458            editor.selections.display_ranges(cx),
 5459            vec![
 5460                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5461                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5462                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5463                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5464            ]
 5465        );
 5466    });
 5467
 5468    let editor = cx.add_window(|window, cx| {
 5469        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5470        build_editor(buffer, window, cx)
 5471    });
 5472    _ = editor.update(cx, |editor, window, cx| {
 5473        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5474            s.select_display_ranges([
 5475                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5476                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5477            ])
 5478        });
 5479        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5480        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5481        assert_eq!(
 5482            editor.selections.display_ranges(cx),
 5483            vec![
 5484                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5485                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5486            ]
 5487        );
 5488    });
 5489
 5490    let editor = cx.add_window(|window, cx| {
 5491        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5492        build_editor(buffer, window, cx)
 5493    });
 5494    _ = editor.update(cx, |editor, window, cx| {
 5495        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5496            s.select_display_ranges([
 5497                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5498                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5499            ])
 5500        });
 5501        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5502        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5503        assert_eq!(
 5504            editor.selections.display_ranges(cx),
 5505            vec![
 5506                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5507                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5508            ]
 5509        );
 5510    });
 5511}
 5512
 5513#[gpui::test]
 5514fn test_move_line_up_down(cx: &mut TestAppContext) {
 5515    init_test(cx, |_| {});
 5516
 5517    let editor = cx.add_window(|window, cx| {
 5518        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5519        build_editor(buffer, window, cx)
 5520    });
 5521    _ = editor.update(cx, |editor, window, cx| {
 5522        editor.fold_creases(
 5523            vec![
 5524                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5525                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5526                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5527            ],
 5528            true,
 5529            window,
 5530            cx,
 5531        );
 5532        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5533            s.select_display_ranges([
 5534                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5535                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5536                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5537                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5538            ])
 5539        });
 5540        assert_eq!(
 5541            editor.display_text(cx),
 5542            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5543        );
 5544
 5545        editor.move_line_up(&MoveLineUp, window, cx);
 5546        assert_eq!(
 5547            editor.display_text(cx),
 5548            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5549        );
 5550        assert_eq!(
 5551            editor.selections.display_ranges(cx),
 5552            vec![
 5553                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5554                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5555                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5556                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5557            ]
 5558        );
 5559    });
 5560
 5561    _ = editor.update(cx, |editor, window, cx| {
 5562        editor.move_line_down(&MoveLineDown, window, cx);
 5563        assert_eq!(
 5564            editor.display_text(cx),
 5565            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5566        );
 5567        assert_eq!(
 5568            editor.selections.display_ranges(cx),
 5569            vec![
 5570                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5571                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5572                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5573                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5574            ]
 5575        );
 5576    });
 5577
 5578    _ = editor.update(cx, |editor, window, cx| {
 5579        editor.move_line_down(&MoveLineDown, window, cx);
 5580        assert_eq!(
 5581            editor.display_text(cx),
 5582            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5583        );
 5584        assert_eq!(
 5585            editor.selections.display_ranges(cx),
 5586            vec![
 5587                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5588                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5589                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5590                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5591            ]
 5592        );
 5593    });
 5594
 5595    _ = editor.update(cx, |editor, window, cx| {
 5596        editor.move_line_up(&MoveLineUp, window, cx);
 5597        assert_eq!(
 5598            editor.display_text(cx),
 5599            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5600        );
 5601        assert_eq!(
 5602            editor.selections.display_ranges(cx),
 5603            vec![
 5604                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5605                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5606                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5607                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5608            ]
 5609        );
 5610    });
 5611}
 5612
 5613#[gpui::test]
 5614fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5615    init_test(cx, |_| {});
 5616    let editor = cx.add_window(|window, cx| {
 5617        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5618        build_editor(buffer, window, cx)
 5619    });
 5620    _ = editor.update(cx, |editor, window, cx| {
 5621        editor.fold_creases(
 5622            vec![Crease::simple(
 5623                Point::new(6, 4)..Point::new(7, 4),
 5624                FoldPlaceholder::test(),
 5625            )],
 5626            true,
 5627            window,
 5628            cx,
 5629        );
 5630        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5631            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5632        });
 5633        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5634        editor.move_line_up(&MoveLineUp, window, cx);
 5635        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5636        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5637    });
 5638}
 5639
 5640#[gpui::test]
 5641fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5642    init_test(cx, |_| {});
 5643
 5644    let editor = cx.add_window(|window, cx| {
 5645        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5646        build_editor(buffer, window, cx)
 5647    });
 5648    _ = editor.update(cx, |editor, window, cx| {
 5649        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5650        editor.insert_blocks(
 5651            [BlockProperties {
 5652                style: BlockStyle::Fixed,
 5653                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5654                height: Some(1),
 5655                render: Arc::new(|_| div().into_any()),
 5656                priority: 0,
 5657            }],
 5658            Some(Autoscroll::fit()),
 5659            cx,
 5660        );
 5661        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5662            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5663        });
 5664        editor.move_line_down(&MoveLineDown, window, cx);
 5665    });
 5666}
 5667
 5668#[gpui::test]
 5669async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5670    init_test(cx, |_| {});
 5671
 5672    let mut cx = EditorTestContext::new(cx).await;
 5673    cx.set_state(
 5674        &"
 5675            ˇzero
 5676            one
 5677            two
 5678            three
 5679            four
 5680            five
 5681        "
 5682        .unindent(),
 5683    );
 5684
 5685    // Create a four-line block that replaces three lines of text.
 5686    cx.update_editor(|editor, window, cx| {
 5687        let snapshot = editor.snapshot(window, cx);
 5688        let snapshot = &snapshot.buffer_snapshot;
 5689        let placement = BlockPlacement::Replace(
 5690            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5691        );
 5692        editor.insert_blocks(
 5693            [BlockProperties {
 5694                placement,
 5695                height: Some(4),
 5696                style: BlockStyle::Sticky,
 5697                render: Arc::new(|_| gpui::div().into_any_element()),
 5698                priority: 0,
 5699            }],
 5700            None,
 5701            cx,
 5702        );
 5703    });
 5704
 5705    // Move down so that the cursor touches the block.
 5706    cx.update_editor(|editor, window, cx| {
 5707        editor.move_down(&Default::default(), window, cx);
 5708    });
 5709    cx.assert_editor_state(
 5710        &"
 5711            zero
 5712            «one
 5713            two
 5714            threeˇ»
 5715            four
 5716            five
 5717        "
 5718        .unindent(),
 5719    );
 5720
 5721    // Move down past the block.
 5722    cx.update_editor(|editor, window, cx| {
 5723        editor.move_down(&Default::default(), window, cx);
 5724    });
 5725    cx.assert_editor_state(
 5726        &"
 5727            zero
 5728            one
 5729            two
 5730            three
 5731            ˇfour
 5732            five
 5733        "
 5734        .unindent(),
 5735    );
 5736}
 5737
 5738#[gpui::test]
 5739fn test_transpose(cx: &mut TestAppContext) {
 5740    init_test(cx, |_| {});
 5741
 5742    _ = cx.add_window(|window, cx| {
 5743        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5744        editor.set_style(EditorStyle::default(), window, cx);
 5745        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5746            s.select_ranges([1..1])
 5747        });
 5748        editor.transpose(&Default::default(), window, cx);
 5749        assert_eq!(editor.text(cx), "bac");
 5750        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5751
 5752        editor.transpose(&Default::default(), window, cx);
 5753        assert_eq!(editor.text(cx), "bca");
 5754        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5755
 5756        editor.transpose(&Default::default(), window, cx);
 5757        assert_eq!(editor.text(cx), "bac");
 5758        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5759
 5760        editor
 5761    });
 5762
 5763    _ = cx.add_window(|window, cx| {
 5764        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5765        editor.set_style(EditorStyle::default(), window, cx);
 5766        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5767            s.select_ranges([3..3])
 5768        });
 5769        editor.transpose(&Default::default(), window, cx);
 5770        assert_eq!(editor.text(cx), "acb\nde");
 5771        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5772
 5773        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5774            s.select_ranges([4..4])
 5775        });
 5776        editor.transpose(&Default::default(), window, cx);
 5777        assert_eq!(editor.text(cx), "acbd\ne");
 5778        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5779
 5780        editor.transpose(&Default::default(), window, cx);
 5781        assert_eq!(editor.text(cx), "acbde\n");
 5782        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5783
 5784        editor.transpose(&Default::default(), window, cx);
 5785        assert_eq!(editor.text(cx), "acbd\ne");
 5786        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5787
 5788        editor
 5789    });
 5790
 5791    _ = cx.add_window(|window, cx| {
 5792        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5793        editor.set_style(EditorStyle::default(), window, cx);
 5794        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5795            s.select_ranges([1..1, 2..2, 4..4])
 5796        });
 5797        editor.transpose(&Default::default(), window, cx);
 5798        assert_eq!(editor.text(cx), "bacd\ne");
 5799        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5800
 5801        editor.transpose(&Default::default(), window, cx);
 5802        assert_eq!(editor.text(cx), "bcade\n");
 5803        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5804
 5805        editor.transpose(&Default::default(), window, cx);
 5806        assert_eq!(editor.text(cx), "bcda\ne");
 5807        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5808
 5809        editor.transpose(&Default::default(), window, cx);
 5810        assert_eq!(editor.text(cx), "bcade\n");
 5811        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5812
 5813        editor.transpose(&Default::default(), window, cx);
 5814        assert_eq!(editor.text(cx), "bcaed\n");
 5815        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5816
 5817        editor
 5818    });
 5819
 5820    _ = cx.add_window(|window, cx| {
 5821        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", 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([4..4])
 5825        });
 5826        editor.transpose(&Default::default(), window, cx);
 5827        assert_eq!(editor.text(cx), "🏀🍐✋");
 5828        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5829
 5830        editor.transpose(&Default::default(), window, cx);
 5831        assert_eq!(editor.text(cx), "🏀✋🍐");
 5832        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5833
 5834        editor.transpose(&Default::default(), window, cx);
 5835        assert_eq!(editor.text(cx), "🏀🍐✋");
 5836        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5837
 5838        editor
 5839    });
 5840}
 5841
 5842#[gpui::test]
 5843async fn test_rewrap(cx: &mut TestAppContext) {
 5844    init_test(cx, |settings| {
 5845        settings.languages.0.extend([
 5846            (
 5847                "Markdown".into(),
 5848                LanguageSettingsContent {
 5849                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5850                    preferred_line_length: Some(40),
 5851                    ..Default::default()
 5852                },
 5853            ),
 5854            (
 5855                "Plain Text".into(),
 5856                LanguageSettingsContent {
 5857                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5858                    preferred_line_length: Some(40),
 5859                    ..Default::default()
 5860                },
 5861            ),
 5862            (
 5863                "C++".into(),
 5864                LanguageSettingsContent {
 5865                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5866                    preferred_line_length: Some(40),
 5867                    ..Default::default()
 5868                },
 5869            ),
 5870            (
 5871                "Python".into(),
 5872                LanguageSettingsContent {
 5873                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5874                    preferred_line_length: Some(40),
 5875                    ..Default::default()
 5876                },
 5877            ),
 5878            (
 5879                "Rust".into(),
 5880                LanguageSettingsContent {
 5881                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5882                    preferred_line_length: Some(40),
 5883                    ..Default::default()
 5884                },
 5885            ),
 5886        ])
 5887    });
 5888
 5889    let mut cx = EditorTestContext::new(cx).await;
 5890
 5891    let cpp_language = Arc::new(Language::new(
 5892        LanguageConfig {
 5893            name: "C++".into(),
 5894            line_comments: vec!["// ".into()],
 5895            ..LanguageConfig::default()
 5896        },
 5897        None,
 5898    ));
 5899    let python_language = Arc::new(Language::new(
 5900        LanguageConfig {
 5901            name: "Python".into(),
 5902            line_comments: vec!["# ".into()],
 5903            ..LanguageConfig::default()
 5904        },
 5905        None,
 5906    ));
 5907    let markdown_language = Arc::new(Language::new(
 5908        LanguageConfig {
 5909            name: "Markdown".into(),
 5910            rewrap_prefixes: vec![
 5911                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 5912                regex::Regex::new("[-*+]\\s+").unwrap(),
 5913            ],
 5914            ..LanguageConfig::default()
 5915        },
 5916        None,
 5917    ));
 5918    let rust_language = Arc::new(
 5919        Language::new(
 5920            LanguageConfig {
 5921                name: "Rust".into(),
 5922                line_comments: vec!["// ".into(), "/// ".into()],
 5923                ..LanguageConfig::default()
 5924            },
 5925            Some(tree_sitter_rust::LANGUAGE.into()),
 5926        )
 5927        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 5928        .unwrap(),
 5929    );
 5930
 5931    let plaintext_language = Arc::new(Language::new(
 5932        LanguageConfig {
 5933            name: "Plain Text".into(),
 5934            ..LanguageConfig::default()
 5935        },
 5936        None,
 5937    ));
 5938
 5939    // Test basic rewrapping of a long line with a cursor
 5940    assert_rewrap(
 5941        indoc! {"
 5942            // ˇThis is a long comment that needs to be wrapped.
 5943        "},
 5944        indoc! {"
 5945            // ˇThis is a long comment that needs to
 5946            // be wrapped.
 5947        "},
 5948        cpp_language.clone(),
 5949        &mut cx,
 5950    );
 5951
 5952    // Test rewrapping a full selection
 5953    assert_rewrap(
 5954        indoc! {"
 5955            «// This selected long comment needs to be wrapped.ˇ»"
 5956        },
 5957        indoc! {"
 5958            «// This selected long comment needs to
 5959            // be wrapped.ˇ»"
 5960        },
 5961        cpp_language.clone(),
 5962        &mut cx,
 5963    );
 5964
 5965    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 5966    assert_rewrap(
 5967        indoc! {"
 5968            // ˇThis is the first line.
 5969            // Thisˇ is the second line.
 5970            // This is the thirdˇ line, all part of one paragraph.
 5971         "},
 5972        indoc! {"
 5973            // ˇThis is the first line. Thisˇ is the
 5974            // second line. This is the thirdˇ line,
 5975            // all part of one paragraph.
 5976         "},
 5977        cpp_language.clone(),
 5978        &mut cx,
 5979    );
 5980
 5981    // Test multiple cursors in different paragraphs trigger separate rewraps
 5982    assert_rewrap(
 5983        indoc! {"
 5984            // ˇThis is the first paragraph, first line.
 5985            // ˇThis is the first paragraph, second line.
 5986
 5987            // ˇThis is the second paragraph, first line.
 5988            // ˇThis is the second paragraph, second line.
 5989        "},
 5990        indoc! {"
 5991            // ˇThis is the first paragraph, first
 5992            // line. ˇThis is the first paragraph,
 5993            // second line.
 5994
 5995            // ˇThis is the second paragraph, first
 5996            // line. ˇThis is the second paragraph,
 5997            // second line.
 5998        "},
 5999        cpp_language.clone(),
 6000        &mut cx,
 6001    );
 6002
 6003    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 6004    assert_rewrap(
 6005        indoc! {"
 6006            «// A regular long long comment to be wrapped.
 6007            /// A documentation long comment to be wrapped.ˇ»
 6008          "},
 6009        indoc! {"
 6010            «// A regular long long comment to be
 6011            // wrapped.
 6012            /// A documentation long comment to be
 6013            /// wrapped.ˇ»
 6014          "},
 6015        rust_language.clone(),
 6016        &mut cx,
 6017    );
 6018
 6019    // Test that change in indentation level trigger seperate rewraps
 6020    assert_rewrap(
 6021        indoc! {"
 6022            fn foo() {
 6023                «// This is a long comment at the base indent.
 6024                    // This is a long comment at the next indent.ˇ»
 6025            }
 6026        "},
 6027        indoc! {"
 6028            fn foo() {
 6029                «// This is a long comment at the
 6030                // base indent.
 6031                    // This is a long comment at the
 6032                    // next indent.ˇ»
 6033            }
 6034        "},
 6035        rust_language.clone(),
 6036        &mut cx,
 6037    );
 6038
 6039    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6040    assert_rewrap(
 6041        indoc! {"
 6042            # ˇThis is a long comment using a pound sign.
 6043        "},
 6044        indoc! {"
 6045            # ˇThis is a long comment using a pound
 6046            # sign.
 6047        "},
 6048        python_language,
 6049        &mut cx,
 6050    );
 6051
 6052    // Test rewrapping only affects comments, not code even when selected
 6053    assert_rewrap(
 6054        indoc! {"
 6055            «/// This doc comment is long and should be wrapped.
 6056            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6057        "},
 6058        indoc! {"
 6059            «/// This doc comment is long and should
 6060            /// be wrapped.
 6061            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6062        "},
 6063        rust_language.clone(),
 6064        &mut cx,
 6065    );
 6066
 6067    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6068    assert_rewrap(
 6069        indoc! {"
 6070            # Header
 6071
 6072            A long long long line of markdown text to wrap.ˇ
 6073         "},
 6074        indoc! {"
 6075            # Header
 6076
 6077            A long long long line of markdown text
 6078            to wrap.ˇ
 6079         "},
 6080        markdown_language.clone(),
 6081        &mut cx,
 6082    );
 6083
 6084    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6085    assert_rewrap(
 6086        indoc! {"
 6087            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6088            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6089            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6090        "},
 6091        indoc! {"
 6092            «1. This is a numbered list item that is
 6093               very long and needs to be wrapped
 6094               properly.
 6095            2. This is a numbered list item that is
 6096               very long and needs to be wrapped
 6097               properly.
 6098            - This is an unordered list item that is
 6099              also very long and should not merge
 6100              with the numbered item.ˇ»
 6101        "},
 6102        markdown_language.clone(),
 6103        &mut cx,
 6104    );
 6105
 6106    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6107    assert_rewrap(
 6108        indoc! {"
 6109            «1. This is a numbered list item that is
 6110            very long and needs to be wrapped
 6111            properly.
 6112            2. This is a numbered list item that is
 6113            very long and needs to be wrapped
 6114            properly.
 6115            - This is an unordered list item that is
 6116            also very long and should not merge with
 6117            the numbered item.ˇ»
 6118        "},
 6119        indoc! {"
 6120            «1. This is a numbered list item that is
 6121               very long and needs to be wrapped
 6122               properly.
 6123            2. This is a numbered list item that is
 6124               very long and needs to be wrapped
 6125               properly.
 6126            - This is an unordered list item that is
 6127              also very long and should not merge
 6128              with the numbered item.ˇ»
 6129        "},
 6130        markdown_language.clone(),
 6131        &mut cx,
 6132    );
 6133
 6134    // Test that rewrapping maintain indents even when they already exists.
 6135    assert_rewrap(
 6136        indoc! {"
 6137            «1. This is a numbered list
 6138               item that is very long and needs to be wrapped properly.
 6139            2. This is a numbered list
 6140               item that is very long and needs to be wrapped properly.
 6141            - This is an unordered list item that is also very long and
 6142              should not merge with the numbered item.ˇ»
 6143        "},
 6144        indoc! {"
 6145            «1. This is a numbered list item that is
 6146               very long and needs to be wrapped
 6147               properly.
 6148            2. This is a numbered list item that is
 6149               very long and needs to be wrapped
 6150               properly.
 6151            - This is an unordered list item that is
 6152              also very long and should not merge
 6153              with the numbered item.ˇ»
 6154        "},
 6155        markdown_language,
 6156        &mut cx,
 6157    );
 6158
 6159    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6160    assert_rewrap(
 6161        indoc! {"
 6162            ˇThis is a very long line of plain text that will be wrapped.
 6163        "},
 6164        indoc! {"
 6165            ˇThis is a very long line of plain text
 6166            that will be wrapped.
 6167        "},
 6168        plaintext_language.clone(),
 6169        &mut cx,
 6170    );
 6171
 6172    // Test that non-commented code acts as a paragraph boundary within a selection
 6173    assert_rewrap(
 6174        indoc! {"
 6175               «// This is the first long comment block to be wrapped.
 6176               fn my_func(a: u32);
 6177               // This is the second long comment block to be wrapped.ˇ»
 6178           "},
 6179        indoc! {"
 6180               «// This is the first long comment block
 6181               // to be wrapped.
 6182               fn my_func(a: u32);
 6183               // This is the second long comment block
 6184               // to be wrapped.ˇ»
 6185           "},
 6186        rust_language,
 6187        &mut cx,
 6188    );
 6189
 6190    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6191    assert_rewrap(
 6192        indoc! {"
 6193            «ˇThis is a very long line that will be wrapped.
 6194
 6195            This is another paragraph in the same selection.»
 6196
 6197            «\tThis is a very long indented line that will be wrapped.ˇ»
 6198         "},
 6199        indoc! {"
 6200            «ˇThis is a very long line that will be
 6201            wrapped.
 6202
 6203            This is another paragraph in the same
 6204            selection.»
 6205
 6206            «\tThis is a very long indented line
 6207            \tthat will be wrapped.ˇ»
 6208         "},
 6209        plaintext_language,
 6210        &mut cx,
 6211    );
 6212
 6213    // Test that an empty comment line acts as a paragraph boundary
 6214    assert_rewrap(
 6215        indoc! {"
 6216            // ˇThis is a long comment that will be wrapped.
 6217            //
 6218            // And this is another long comment that will also be wrapped.ˇ
 6219         "},
 6220        indoc! {"
 6221            // ˇThis is a long comment that will be
 6222            // wrapped.
 6223            //
 6224            // And this is another long comment that
 6225            // will also be wrapped.ˇ
 6226         "},
 6227        cpp_language,
 6228        &mut cx,
 6229    );
 6230
 6231    #[track_caller]
 6232    fn assert_rewrap(
 6233        unwrapped_text: &str,
 6234        wrapped_text: &str,
 6235        language: Arc<Language>,
 6236        cx: &mut EditorTestContext,
 6237    ) {
 6238        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6239        cx.set_state(unwrapped_text);
 6240        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6241        cx.assert_editor_state(wrapped_text);
 6242    }
 6243}
 6244
 6245#[gpui::test]
 6246async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6247    init_test(cx, |settings| {
 6248        settings.languages.0.extend([(
 6249            "Rust".into(),
 6250            LanguageSettingsContent {
 6251                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6252                preferred_line_length: Some(40),
 6253                ..Default::default()
 6254            },
 6255        )])
 6256    });
 6257
 6258    let mut cx = EditorTestContext::new(cx).await;
 6259
 6260    let rust_lang = Arc::new(
 6261        Language::new(
 6262            LanguageConfig {
 6263                name: "Rust".into(),
 6264                line_comments: vec!["// ".into()],
 6265                block_comment: Some(BlockCommentConfig {
 6266                    start: "/*".into(),
 6267                    end: "*/".into(),
 6268                    prefix: "* ".into(),
 6269                    tab_size: 1,
 6270                }),
 6271                documentation_comment: Some(BlockCommentConfig {
 6272                    start: "/**".into(),
 6273                    end: "*/".into(),
 6274                    prefix: "* ".into(),
 6275                    tab_size: 1,
 6276                }),
 6277
 6278                ..LanguageConfig::default()
 6279            },
 6280            Some(tree_sitter_rust::LANGUAGE.into()),
 6281        )
 6282        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6283        .unwrap(),
 6284    );
 6285
 6286    // regular block comment
 6287    assert_rewrap(
 6288        indoc! {"
 6289            /*
 6290             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6291             */
 6292            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6293        "},
 6294        indoc! {"
 6295            /*
 6296             *ˇ Lorem ipsum dolor sit amet,
 6297             * consectetur adipiscing elit.
 6298             */
 6299            /*
 6300             *ˇ Lorem ipsum dolor sit amet,
 6301             * consectetur adipiscing elit.
 6302             */
 6303        "},
 6304        rust_lang.clone(),
 6305        &mut cx,
 6306    );
 6307
 6308    // indent is respected
 6309    assert_rewrap(
 6310        indoc! {"
 6311            {}
 6312                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6313        "},
 6314        indoc! {"
 6315            {}
 6316                /*
 6317                 *ˇ Lorem ipsum dolor sit amet,
 6318                 * consectetur adipiscing elit.
 6319                 */
 6320        "},
 6321        rust_lang.clone(),
 6322        &mut cx,
 6323    );
 6324
 6325    // short block comments with inline delimiters
 6326    assert_rewrap(
 6327        indoc! {"
 6328            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6329            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6330             */
 6331            /*
 6332             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6333        "},
 6334        indoc! {"
 6335            /*
 6336             *ˇ Lorem ipsum dolor sit amet,
 6337             * consectetur adipiscing elit.
 6338             */
 6339            /*
 6340             *ˇ Lorem ipsum dolor sit amet,
 6341             * consectetur adipiscing elit.
 6342             */
 6343            /*
 6344             *ˇ Lorem ipsum dolor sit amet,
 6345             * consectetur adipiscing elit.
 6346             */
 6347        "},
 6348        rust_lang.clone(),
 6349        &mut cx,
 6350    );
 6351
 6352    // multiline block comment with inline start/end delimiters
 6353    assert_rewrap(
 6354        indoc! {"
 6355            /*ˇ Lorem ipsum dolor sit amet,
 6356             * consectetur adipiscing elit. */
 6357        "},
 6358        indoc! {"
 6359            /*
 6360             *ˇ Lorem ipsum dolor sit amet,
 6361             * consectetur adipiscing elit.
 6362             */
 6363        "},
 6364        rust_lang.clone(),
 6365        &mut cx,
 6366    );
 6367
 6368    // block comment rewrap still respects paragraph bounds
 6369    assert_rewrap(
 6370        indoc! {"
 6371            /*
 6372             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6373             *
 6374             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6375             */
 6376        "},
 6377        indoc! {"
 6378            /*
 6379             *ˇ Lorem ipsum dolor sit amet,
 6380             * consectetur adipiscing elit.
 6381             *
 6382             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6383             */
 6384        "},
 6385        rust_lang.clone(),
 6386        &mut cx,
 6387    );
 6388
 6389    // documentation comments
 6390    assert_rewrap(
 6391        indoc! {"
 6392            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6393            /**
 6394             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6395             */
 6396        "},
 6397        indoc! {"
 6398            /**
 6399             *ˇ Lorem ipsum dolor sit amet,
 6400             * consectetur adipiscing elit.
 6401             */
 6402            /**
 6403             *ˇ Lorem ipsum dolor sit amet,
 6404             * consectetur adipiscing elit.
 6405             */
 6406        "},
 6407        rust_lang.clone(),
 6408        &mut cx,
 6409    );
 6410
 6411    // different, adjacent comments
 6412    assert_rewrap(
 6413        indoc! {"
 6414            /**
 6415             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6416             */
 6417            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6418            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6419        "},
 6420        indoc! {"
 6421            /**
 6422             *ˇ Lorem ipsum dolor sit amet,
 6423             * consectetur adipiscing elit.
 6424             */
 6425            /*
 6426             *ˇ Lorem ipsum dolor sit amet,
 6427             * consectetur adipiscing elit.
 6428             */
 6429            //ˇ Lorem ipsum dolor sit amet,
 6430            // consectetur adipiscing elit.
 6431        "},
 6432        rust_lang.clone(),
 6433        &mut cx,
 6434    );
 6435
 6436    // selection w/ single short block comment
 6437    assert_rewrap(
 6438        indoc! {"
 6439            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6440        "},
 6441        indoc! {"
 6442            «/*
 6443             * Lorem ipsum dolor sit amet,
 6444             * consectetur adipiscing elit.
 6445             */ˇ»
 6446        "},
 6447        rust_lang.clone(),
 6448        &mut cx,
 6449    );
 6450
 6451    // rewrapping a single comment w/ abutting comments
 6452    assert_rewrap(
 6453        indoc! {"
 6454            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6455            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6456        "},
 6457        indoc! {"
 6458            /*
 6459             * ˇLorem ipsum dolor sit amet,
 6460             * consectetur adipiscing elit.
 6461             */
 6462            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6463        "},
 6464        rust_lang.clone(),
 6465        &mut cx,
 6466    );
 6467
 6468    // selection w/ non-abutting short block comments
 6469    assert_rewrap(
 6470        indoc! {"
 6471            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6472
 6473            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6474        "},
 6475        indoc! {"
 6476            «/*
 6477             * Lorem ipsum dolor sit amet,
 6478             * consectetur adipiscing elit.
 6479             */
 6480
 6481            /*
 6482             * Lorem ipsum dolor sit amet,
 6483             * consectetur adipiscing elit.
 6484             */ˇ»
 6485        "},
 6486        rust_lang.clone(),
 6487        &mut cx,
 6488    );
 6489
 6490    // selection of multiline block comments
 6491    assert_rewrap(
 6492        indoc! {"
 6493            «/* Lorem ipsum dolor sit amet,
 6494             * consectetur adipiscing elit. */ˇ»
 6495        "},
 6496        indoc! {"
 6497            «/*
 6498             * Lorem ipsum dolor sit amet,
 6499             * consectetur adipiscing elit.
 6500             */ˇ»
 6501        "},
 6502        rust_lang.clone(),
 6503        &mut cx,
 6504    );
 6505
 6506    // partial selection of multiline block comments
 6507    assert_rewrap(
 6508        indoc! {"
 6509            «/* Lorem ipsum dolor sit amet,ˇ»
 6510             * consectetur adipiscing elit. */
 6511            /* Lorem ipsum dolor sit amet,
 6512             «* consectetur adipiscing elit. */ˇ»
 6513        "},
 6514        indoc! {"
 6515            «/*
 6516             * Lorem ipsum dolor sit amet,ˇ»
 6517             * consectetur adipiscing elit. */
 6518            /* Lorem ipsum dolor sit amet,
 6519             «* consectetur adipiscing elit.
 6520             */ˇ»
 6521        "},
 6522        rust_lang.clone(),
 6523        &mut cx,
 6524    );
 6525
 6526    // selection w/ abutting short block comments
 6527    // TODO: should not be combined; should rewrap as 2 comments
 6528    assert_rewrap(
 6529        indoc! {"
 6530            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6531            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6532        "},
 6533        // desired behavior:
 6534        // indoc! {"
 6535        //     «/*
 6536        //      * Lorem ipsum dolor sit amet,
 6537        //      * consectetur adipiscing elit.
 6538        //      */
 6539        //     /*
 6540        //      * Lorem ipsum dolor sit amet,
 6541        //      * consectetur adipiscing elit.
 6542        //      */ˇ»
 6543        // "},
 6544        // actual behaviour:
 6545        indoc! {"
 6546            «/*
 6547             * Lorem ipsum dolor sit amet,
 6548             * consectetur adipiscing elit. Lorem
 6549             * ipsum dolor sit amet, consectetur
 6550             * adipiscing elit.
 6551             */ˇ»
 6552        "},
 6553        rust_lang.clone(),
 6554        &mut cx,
 6555    );
 6556
 6557    // TODO: same as above, but with delimiters on separate line
 6558    // assert_rewrap(
 6559    //     indoc! {"
 6560    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6561    //          */
 6562    //         /*
 6563    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6564    //     "},
 6565    //     // desired:
 6566    //     // indoc! {"
 6567    //     //     «/*
 6568    //     //      * Lorem ipsum dolor sit amet,
 6569    //     //      * consectetur adipiscing elit.
 6570    //     //      */
 6571    //     //     /*
 6572    //     //      * Lorem ipsum dolor sit amet,
 6573    //     //      * consectetur adipiscing elit.
 6574    //     //      */ˇ»
 6575    //     // "},
 6576    //     // actual: (but with trailing w/s on the empty lines)
 6577    //     indoc! {"
 6578    //         «/*
 6579    //          * Lorem ipsum dolor sit amet,
 6580    //          * consectetur adipiscing elit.
 6581    //          *
 6582    //          */
 6583    //         /*
 6584    //          *
 6585    //          * Lorem ipsum dolor sit amet,
 6586    //          * consectetur adipiscing elit.
 6587    //          */ˇ»
 6588    //     "},
 6589    //     rust_lang.clone(),
 6590    //     &mut cx,
 6591    // );
 6592
 6593    // TODO these are unhandled edge cases; not correct, just documenting known issues
 6594    assert_rewrap(
 6595        indoc! {"
 6596            /*
 6597             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6598             */
 6599            /*
 6600             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6601            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 6602        "},
 6603        // desired:
 6604        // indoc! {"
 6605        //     /*
 6606        //      *ˇ Lorem ipsum dolor sit amet,
 6607        //      * consectetur adipiscing elit.
 6608        //      */
 6609        //     /*
 6610        //      *ˇ Lorem ipsum dolor sit amet,
 6611        //      * consectetur adipiscing elit.
 6612        //      */
 6613        //     /*
 6614        //      *ˇ Lorem ipsum dolor sit amet
 6615        //      */ /* consectetur adipiscing elit. */
 6616        // "},
 6617        // actual:
 6618        indoc! {"
 6619            /*
 6620             //ˇ Lorem ipsum dolor sit amet,
 6621             // consectetur adipiscing elit.
 6622             */
 6623            /*
 6624             * //ˇ Lorem ipsum dolor sit amet,
 6625             * consectetur adipiscing elit.
 6626             */
 6627            /*
 6628             *ˇ Lorem ipsum dolor sit amet */ /*
 6629             * consectetur adipiscing elit.
 6630             */
 6631        "},
 6632        rust_lang,
 6633        &mut cx,
 6634    );
 6635
 6636    #[track_caller]
 6637    fn assert_rewrap(
 6638        unwrapped_text: &str,
 6639        wrapped_text: &str,
 6640        language: Arc<Language>,
 6641        cx: &mut EditorTestContext,
 6642    ) {
 6643        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6644        cx.set_state(unwrapped_text);
 6645        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6646        cx.assert_editor_state(wrapped_text);
 6647    }
 6648}
 6649
 6650#[gpui::test]
 6651async fn test_hard_wrap(cx: &mut TestAppContext) {
 6652    init_test(cx, |_| {});
 6653    let mut cx = EditorTestContext::new(cx).await;
 6654
 6655    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 6656    cx.update_editor(|editor, _, cx| {
 6657        editor.set_hard_wrap(Some(14), cx);
 6658    });
 6659
 6660    cx.set_state(indoc!(
 6661        "
 6662        one two three ˇ
 6663        "
 6664    ));
 6665    cx.simulate_input("four");
 6666    cx.run_until_parked();
 6667
 6668    cx.assert_editor_state(indoc!(
 6669        "
 6670        one two three
 6671        fourˇ
 6672        "
 6673    ));
 6674
 6675    cx.update_editor(|editor, window, cx| {
 6676        editor.newline(&Default::default(), window, cx);
 6677    });
 6678    cx.run_until_parked();
 6679    cx.assert_editor_state(indoc!(
 6680        "
 6681        one two three
 6682        four
 6683        ˇ
 6684        "
 6685    ));
 6686
 6687    cx.simulate_input("five");
 6688    cx.run_until_parked();
 6689    cx.assert_editor_state(indoc!(
 6690        "
 6691        one two three
 6692        four
 6693        fiveˇ
 6694        "
 6695    ));
 6696
 6697    cx.update_editor(|editor, window, cx| {
 6698        editor.newline(&Default::default(), window, cx);
 6699    });
 6700    cx.run_until_parked();
 6701    cx.simulate_input("# ");
 6702    cx.run_until_parked();
 6703    cx.assert_editor_state(indoc!(
 6704        "
 6705        one two three
 6706        four
 6707        five
 6708        # ˇ
 6709        "
 6710    ));
 6711
 6712    cx.update_editor(|editor, window, cx| {
 6713        editor.newline(&Default::default(), window, cx);
 6714    });
 6715    cx.run_until_parked();
 6716    cx.assert_editor_state(indoc!(
 6717        "
 6718        one two three
 6719        four
 6720        five
 6721        #\x20
 6722 6723        "
 6724    ));
 6725
 6726    cx.simulate_input(" 6");
 6727    cx.run_until_parked();
 6728    cx.assert_editor_state(indoc!(
 6729        "
 6730        one two three
 6731        four
 6732        five
 6733        #
 6734        # 6ˇ
 6735        "
 6736    ));
 6737}
 6738
 6739#[gpui::test]
 6740async fn test_cut_line_ends(cx: &mut TestAppContext) {
 6741    init_test(cx, |_| {});
 6742
 6743    let mut cx = EditorTestContext::new(cx).await;
 6744
 6745    cx.set_state(indoc! {"The quick brownˇ"});
 6746    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 6747    cx.assert_editor_state(indoc! {"The quick brownˇ"});
 6748
 6749    cx.set_state(indoc! {"The emacs foxˇ"});
 6750    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 6751    cx.assert_editor_state(indoc! {"The emacs foxˇ"});
 6752
 6753    cx.set_state(indoc! {"
 6754        The quick« brownˇ»
 6755        fox jumps overˇ
 6756        the lazy dog"});
 6757    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6758    cx.assert_editor_state(indoc! {"
 6759        The quickˇ
 6760        ˇthe lazy dog"});
 6761
 6762    cx.set_state(indoc! {"
 6763        The quick« brownˇ»
 6764        fox jumps overˇ
 6765        the lazy dog"});
 6766    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 6767    cx.assert_editor_state(indoc! {"
 6768        The quickˇ
 6769        fox jumps overˇthe lazy dog"});
 6770
 6771    cx.set_state(indoc! {"
 6772        The quick« brownˇ»
 6773        fox jumps overˇ
 6774        the lazy dog"});
 6775    cx.update_editor(|e, window, cx| {
 6776        e.cut_to_end_of_line(
 6777            &CutToEndOfLine {
 6778                stop_at_newlines: true,
 6779            },
 6780            window,
 6781            cx,
 6782        )
 6783    });
 6784    cx.assert_editor_state(indoc! {"
 6785        The quickˇ
 6786        fox jumps overˇ
 6787        the lazy dog"});
 6788
 6789    cx.set_state(indoc! {"
 6790        The quick« brownˇ»
 6791        fox jumps overˇ
 6792        the lazy dog"});
 6793    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 6794    cx.assert_editor_state(indoc! {"
 6795        The quickˇ
 6796        fox jumps overˇthe lazy dog"});
 6797}
 6798
 6799#[gpui::test]
 6800async fn test_clipboard(cx: &mut TestAppContext) {
 6801    init_test(cx, |_| {});
 6802
 6803    let mut cx = EditorTestContext::new(cx).await;
 6804
 6805    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 6806    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6807    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 6808
 6809    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 6810    cx.set_state("two ˇfour ˇsix ˇ");
 6811    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6812    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 6813
 6814    // Paste again but with only two cursors. Since the number of cursors doesn't
 6815    // match the number of slices in the clipboard, the entire clipboard text
 6816    // is pasted at each cursor.
 6817    cx.set_state("ˇtwo one✅ four three six five ˇ");
 6818    cx.update_editor(|e, window, cx| {
 6819        e.handle_input("( ", window, cx);
 6820        e.paste(&Paste, window, cx);
 6821        e.handle_input(") ", window, cx);
 6822    });
 6823    cx.assert_editor_state(
 6824        &([
 6825            "( one✅ ",
 6826            "three ",
 6827            "five ) ˇtwo one✅ four three six five ( one✅ ",
 6828            "three ",
 6829            "five ) ˇ",
 6830        ]
 6831        .join("\n")),
 6832    );
 6833
 6834    // Cut with three selections, one of which is full-line.
 6835    cx.set_state(indoc! {"
 6836        1«2ˇ»3
 6837        4ˇ567
 6838        «8ˇ»9"});
 6839    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6840    cx.assert_editor_state(indoc! {"
 6841        1ˇ3
 6842        ˇ9"});
 6843
 6844    // Paste with three selections, noticing how the copied selection that was full-line
 6845    // gets inserted before the second cursor.
 6846    cx.set_state(indoc! {"
 6847        1ˇ3
 6848 6849        «oˇ»ne"});
 6850    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6851    cx.assert_editor_state(indoc! {"
 6852        12ˇ3
 6853        4567
 6854 6855        8ˇne"});
 6856
 6857    // Copy with a single cursor only, which writes the whole line into the clipboard.
 6858    cx.set_state(indoc! {"
 6859        The quick brown
 6860        fox juˇmps over
 6861        the lazy dog"});
 6862    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6863    assert_eq!(
 6864        cx.read_from_clipboard()
 6865            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6866        Some("fox jumps over\n".to_string())
 6867    );
 6868
 6869    // Paste with three selections, noticing how the copied full-line selection is inserted
 6870    // before the empty selections but replaces the selection that is non-empty.
 6871    cx.set_state(indoc! {"
 6872        Tˇhe quick brown
 6873        «foˇ»x jumps over
 6874        tˇhe lazy dog"});
 6875    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6876    cx.assert_editor_state(indoc! {"
 6877        fox jumps over
 6878        Tˇhe quick brown
 6879        fox jumps over
 6880        ˇx jumps over
 6881        fox jumps over
 6882        tˇhe lazy dog"});
 6883}
 6884
 6885#[gpui::test]
 6886async fn test_copy_trim(cx: &mut TestAppContext) {
 6887    init_test(cx, |_| {});
 6888
 6889    let mut cx = EditorTestContext::new(cx).await;
 6890    cx.set_state(
 6891        r#"            «for selection in selections.iter() {
 6892            let mut start = selection.start;
 6893            let mut end = selection.end;
 6894            let is_entire_line = selection.is_empty();
 6895            if is_entire_line {
 6896                start = Point::new(start.row, 0);ˇ»
 6897                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6898            }
 6899        "#,
 6900    );
 6901    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6902    assert_eq!(
 6903        cx.read_from_clipboard()
 6904            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6905        Some(
 6906            "for selection in selections.iter() {
 6907            let mut start = selection.start;
 6908            let mut end = selection.end;
 6909            let is_entire_line = selection.is_empty();
 6910            if is_entire_line {
 6911                start = Point::new(start.row, 0);"
 6912                .to_string()
 6913        ),
 6914        "Regular copying preserves all indentation selected",
 6915    );
 6916    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6917    assert_eq!(
 6918        cx.read_from_clipboard()
 6919            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6920        Some(
 6921            "for selection in selections.iter() {
 6922let mut start = selection.start;
 6923let mut end = selection.end;
 6924let is_entire_line = selection.is_empty();
 6925if is_entire_line {
 6926    start = Point::new(start.row, 0);"
 6927                .to_string()
 6928        ),
 6929        "Copying with stripping should strip all leading whitespaces"
 6930    );
 6931
 6932    cx.set_state(
 6933        r#"       «     for selection in selections.iter() {
 6934            let mut start = selection.start;
 6935            let mut end = selection.end;
 6936            let is_entire_line = selection.is_empty();
 6937            if is_entire_line {
 6938                start = Point::new(start.row, 0);ˇ»
 6939                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6940            }
 6941        "#,
 6942    );
 6943    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6944    assert_eq!(
 6945        cx.read_from_clipboard()
 6946            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6947        Some(
 6948            "     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                .to_string()
 6955        ),
 6956        "Regular copying preserves all indentation selected",
 6957    );
 6958    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, 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() {
 6964let mut start = selection.start;
 6965let mut end = selection.end;
 6966let is_entire_line = selection.is_empty();
 6967if is_entire_line {
 6968    start = Point::new(start.row, 0);"
 6969                .to_string()
 6970        ),
 6971        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 6972    );
 6973
 6974    cx.set_state(
 6975        r#"       «ˇ     for selection in selections.iter() {
 6976            let mut start = selection.start;
 6977            let mut end = selection.end;
 6978            let is_entire_line = selection.is_empty();
 6979            if is_entire_line {
 6980                start = Point::new(start.row, 0);»
 6981                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6982            }
 6983        "#,
 6984    );
 6985    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6986    assert_eq!(
 6987        cx.read_from_clipboard()
 6988            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6989        Some(
 6990            "     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                .to_string()
 6997        ),
 6998        "Regular copying for reverse selection works the same",
 6999    );
 7000    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, 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() {
 7006let mut start = selection.start;
 7007let mut end = selection.end;
 7008let is_entire_line = selection.is_empty();
 7009if is_entire_line {
 7010    start = Point::new(start.row, 0);"
 7011                .to_string()
 7012        ),
 7013        "Copying with stripping for reverse selection works the same"
 7014    );
 7015
 7016    cx.set_state(
 7017        r#"            for selection «in selections.iter() {
 7018            let mut start = selection.start;
 7019            let mut end = selection.end;
 7020            let is_entire_line = selection.is_empty();
 7021            if is_entire_line {
 7022                start = Point::new(start.row, 0);ˇ»
 7023                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7024            }
 7025        "#,
 7026    );
 7027    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7028    assert_eq!(
 7029        cx.read_from_clipboard()
 7030            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7031        Some(
 7032            "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                .to_string()
 7039        ),
 7040        "When selecting past the indent, the copying works as usual",
 7041    );
 7042    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, 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            "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        "When selecting past the indent, nothing is trimmed"
 7056    );
 7057
 7058    cx.set_state(
 7059        r#"            «for selection in selections.iter() {
 7060            let mut start = selection.start;
 7061
 7062            let mut end = selection.end;
 7063            let is_entire_line = selection.is_empty();
 7064            if is_entire_line {
 7065                start = Point::new(start.row, 0);
 7066ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7067            }
 7068        "#,
 7069    );
 7070    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7071    assert_eq!(
 7072        cx.read_from_clipboard()
 7073            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7074        Some(
 7075            "for selection in selections.iter() {
 7076let mut start = selection.start;
 7077
 7078let mut end = selection.end;
 7079let is_entire_line = selection.is_empty();
 7080if is_entire_line {
 7081    start = Point::new(start.row, 0);
 7082"
 7083            .to_string()
 7084        ),
 7085        "Copying with stripping should ignore empty lines"
 7086    );
 7087}
 7088
 7089#[gpui::test]
 7090async fn test_paste_multiline(cx: &mut TestAppContext) {
 7091    init_test(cx, |_| {});
 7092
 7093    let mut cx = EditorTestContext::new(cx).await;
 7094    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7095
 7096    // Cut an indented block, without the leading whitespace.
 7097    cx.set_state(indoc! {"
 7098        const a: B = (
 7099            c(),
 7100            «d(
 7101                e,
 7102                f
 7103            )ˇ»
 7104        );
 7105    "});
 7106    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7107    cx.assert_editor_state(indoc! {"
 7108        const a: B = (
 7109            c(),
 7110            ˇ
 7111        );
 7112    "});
 7113
 7114    // Paste it at the same position.
 7115    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7116    cx.assert_editor_state(indoc! {"
 7117        const a: B = (
 7118            c(),
 7119            d(
 7120                e,
 7121                f
 7122 7123        );
 7124    "});
 7125
 7126    // Paste it at a line with a lower indent level.
 7127    cx.set_state(indoc! {"
 7128        ˇ
 7129        const a: B = (
 7130            c(),
 7131        );
 7132    "});
 7133    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7134    cx.assert_editor_state(indoc! {"
 7135        d(
 7136            e,
 7137            f
 7138 7139        const a: B = (
 7140            c(),
 7141        );
 7142    "});
 7143
 7144    // Cut an indented block, with the leading whitespace.
 7145    cx.set_state(indoc! {"
 7146        const a: B = (
 7147            c(),
 7148        «    d(
 7149                e,
 7150                f
 7151            )
 7152        ˇ»);
 7153    "});
 7154    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7155    cx.assert_editor_state(indoc! {"
 7156        const a: B = (
 7157            c(),
 7158        ˇ);
 7159    "});
 7160
 7161    // Paste it at the same position.
 7162    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7163    cx.assert_editor_state(indoc! {"
 7164        const a: B = (
 7165            c(),
 7166            d(
 7167                e,
 7168                f
 7169            )
 7170        ˇ);
 7171    "});
 7172
 7173    // Paste it at a line with a higher indent level.
 7174    cx.set_state(indoc! {"
 7175        const a: B = (
 7176            c(),
 7177            d(
 7178                e,
 7179 7180            )
 7181        );
 7182    "});
 7183    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7184    cx.assert_editor_state(indoc! {"
 7185        const a: B = (
 7186            c(),
 7187            d(
 7188                e,
 7189                f    d(
 7190                    e,
 7191                    f
 7192                )
 7193        ˇ
 7194            )
 7195        );
 7196    "});
 7197
 7198    // Copy an indented block, starting mid-line
 7199    cx.set_state(indoc! {"
 7200        const a: B = (
 7201            c(),
 7202            somethin«g(
 7203                e,
 7204                f
 7205            )ˇ»
 7206        );
 7207    "});
 7208    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7209
 7210    // Paste it on a line with a lower indent level
 7211    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7212    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7213    cx.assert_editor_state(indoc! {"
 7214        const a: B = (
 7215            c(),
 7216            something(
 7217                e,
 7218                f
 7219            )
 7220        );
 7221        g(
 7222            e,
 7223            f
 7224"});
 7225}
 7226
 7227#[gpui::test]
 7228async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7229    init_test(cx, |_| {});
 7230
 7231    cx.write_to_clipboard(ClipboardItem::new_string(
 7232        "    d(\n        e\n    );\n".into(),
 7233    ));
 7234
 7235    let mut cx = EditorTestContext::new(cx).await;
 7236    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7237
 7238    cx.set_state(indoc! {"
 7239        fn a() {
 7240            b();
 7241            if c() {
 7242                ˇ
 7243            }
 7244        }
 7245    "});
 7246
 7247    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7248    cx.assert_editor_state(indoc! {"
 7249        fn a() {
 7250            b();
 7251            if c() {
 7252                d(
 7253                    e
 7254                );
 7255        ˇ
 7256            }
 7257        }
 7258    "});
 7259
 7260    cx.set_state(indoc! {"
 7261        fn a() {
 7262            b();
 7263            ˇ
 7264        }
 7265    "});
 7266
 7267    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7268    cx.assert_editor_state(indoc! {"
 7269        fn a() {
 7270            b();
 7271            d(
 7272                e
 7273            );
 7274        ˇ
 7275        }
 7276    "});
 7277}
 7278
 7279#[gpui::test]
 7280fn test_select_all(cx: &mut TestAppContext) {
 7281    init_test(cx, |_| {});
 7282
 7283    let editor = cx.add_window(|window, cx| {
 7284        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7285        build_editor(buffer, window, cx)
 7286    });
 7287    _ = editor.update(cx, |editor, window, cx| {
 7288        editor.select_all(&SelectAll, window, cx);
 7289        assert_eq!(
 7290            editor.selections.display_ranges(cx),
 7291            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7292        );
 7293    });
 7294}
 7295
 7296#[gpui::test]
 7297fn test_select_line(cx: &mut TestAppContext) {
 7298    init_test(cx, |_| {});
 7299
 7300    let editor = cx.add_window(|window, cx| {
 7301        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7302        build_editor(buffer, window, cx)
 7303    });
 7304    _ = editor.update(cx, |editor, window, cx| {
 7305        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7306            s.select_display_ranges([
 7307                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7308                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7309                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7310                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7311            ])
 7312        });
 7313        editor.select_line(&SelectLine, window, cx);
 7314        assert_eq!(
 7315            editor.selections.display_ranges(cx),
 7316            vec![
 7317                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7318                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7319            ]
 7320        );
 7321    });
 7322
 7323    _ = editor.update(cx, |editor, window, cx| {
 7324        editor.select_line(&SelectLine, window, cx);
 7325        assert_eq!(
 7326            editor.selections.display_ranges(cx),
 7327            vec![
 7328                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7329                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7330            ]
 7331        );
 7332    });
 7333
 7334    _ = editor.update(cx, |editor, window, cx| {
 7335        editor.select_line(&SelectLine, window, cx);
 7336        assert_eq!(
 7337            editor.selections.display_ranges(cx),
 7338            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 7339        );
 7340    });
 7341}
 7342
 7343#[gpui::test]
 7344async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7345    init_test(cx, |_| {});
 7346    let mut cx = EditorTestContext::new(cx).await;
 7347
 7348    #[track_caller]
 7349    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7350        cx.set_state(initial_state);
 7351        cx.update_editor(|e, window, cx| {
 7352            e.split_selection_into_lines(&Default::default(), window, cx)
 7353        });
 7354        cx.assert_editor_state(expected_state);
 7355    }
 7356
 7357    // Selection starts and ends at the middle of lines, left-to-right
 7358    test(
 7359        &mut cx,
 7360        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7361        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7362    );
 7363    // Same thing, right-to-left
 7364    test(
 7365        &mut cx,
 7366        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7367        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7368    );
 7369
 7370    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7371    test(
 7372        &mut cx,
 7373        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7374        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7375    );
 7376    // Same thing, right-to-left
 7377    test(
 7378        &mut cx,
 7379        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7380        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7381    );
 7382
 7383    // Whole buffer, left-to-right, last line ends with newline
 7384    test(
 7385        &mut cx,
 7386        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7387        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7388    );
 7389    // Same thing, right-to-left
 7390    test(
 7391        &mut cx,
 7392        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 7393        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7394    );
 7395
 7396    // Starts at the end of a line, ends at the start of another
 7397    test(
 7398        &mut cx,
 7399        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 7400        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 7401    );
 7402}
 7403
 7404#[gpui::test]
 7405async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 7406    init_test(cx, |_| {});
 7407
 7408    let editor = cx.add_window(|window, cx| {
 7409        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 7410        build_editor(buffer, window, cx)
 7411    });
 7412
 7413    // setup
 7414    _ = editor.update(cx, |editor, window, cx| {
 7415        editor.fold_creases(
 7416            vec![
 7417                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7418                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7419                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7420            ],
 7421            true,
 7422            window,
 7423            cx,
 7424        );
 7425        assert_eq!(
 7426            editor.display_text(cx),
 7427            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7428        );
 7429    });
 7430
 7431    _ = editor.update(cx, |editor, window, cx| {
 7432        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7433            s.select_display_ranges([
 7434                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7435                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7436                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7437                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7438            ])
 7439        });
 7440        editor.split_selection_into_lines(&Default::default(), window, cx);
 7441        assert_eq!(
 7442            editor.display_text(cx),
 7443            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7444        );
 7445    });
 7446    EditorTestContext::for_editor(editor, cx)
 7447        .await
 7448        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7449
 7450    _ = editor.update(cx, |editor, window, cx| {
 7451        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7452            s.select_display_ranges([
 7453                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7454            ])
 7455        });
 7456        editor.split_selection_into_lines(&Default::default(), window, cx);
 7457        assert_eq!(
 7458            editor.display_text(cx),
 7459            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7460        );
 7461        assert_eq!(
 7462            editor.selections.display_ranges(cx),
 7463            [
 7464                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7465                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7466                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7467                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7468                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7469                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7470                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7471            ]
 7472        );
 7473    });
 7474    EditorTestContext::for_editor(editor, cx)
 7475        .await
 7476        .assert_editor_state(
 7477            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7478        );
 7479}
 7480
 7481#[gpui::test]
 7482async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7483    init_test(cx, |_| {});
 7484
 7485    let mut cx = EditorTestContext::new(cx).await;
 7486
 7487    cx.set_state(indoc!(
 7488        r#"abc
 7489           defˇghi
 7490
 7491           jk
 7492           nlmo
 7493           "#
 7494    ));
 7495
 7496    cx.update_editor(|editor, window, cx| {
 7497        editor.add_selection_above(&Default::default(), window, cx);
 7498    });
 7499
 7500    cx.assert_editor_state(indoc!(
 7501        r#"abcˇ
 7502           defˇghi
 7503
 7504           jk
 7505           nlmo
 7506           "#
 7507    ));
 7508
 7509    cx.update_editor(|editor, window, cx| {
 7510        editor.add_selection_above(&Default::default(), window, cx);
 7511    });
 7512
 7513    cx.assert_editor_state(indoc!(
 7514        r#"abcˇ
 7515            defˇghi
 7516
 7517            jk
 7518            nlmo
 7519            "#
 7520    ));
 7521
 7522    cx.update_editor(|editor, window, cx| {
 7523        editor.add_selection_below(&Default::default(), window, cx);
 7524    });
 7525
 7526    cx.assert_editor_state(indoc!(
 7527        r#"abc
 7528           defˇghi
 7529
 7530           jk
 7531           nlmo
 7532           "#
 7533    ));
 7534
 7535    cx.update_editor(|editor, window, cx| {
 7536        editor.undo_selection(&Default::default(), window, cx);
 7537    });
 7538
 7539    cx.assert_editor_state(indoc!(
 7540        r#"abcˇ
 7541           defˇghi
 7542
 7543           jk
 7544           nlmo
 7545           "#
 7546    ));
 7547
 7548    cx.update_editor(|editor, window, cx| {
 7549        editor.redo_selection(&Default::default(), window, cx);
 7550    });
 7551
 7552    cx.assert_editor_state(indoc!(
 7553        r#"abc
 7554           defˇghi
 7555
 7556           jk
 7557           nlmo
 7558           "#
 7559    ));
 7560
 7561    cx.update_editor(|editor, window, cx| {
 7562        editor.add_selection_below(&Default::default(), window, cx);
 7563    });
 7564
 7565    cx.assert_editor_state(indoc!(
 7566        r#"abc
 7567           defˇghi
 7568           ˇ
 7569           jk
 7570           nlmo
 7571           "#
 7572    ));
 7573
 7574    cx.update_editor(|editor, window, cx| {
 7575        editor.add_selection_below(&Default::default(), window, cx);
 7576    });
 7577
 7578    cx.assert_editor_state(indoc!(
 7579        r#"abc
 7580           defˇghi
 7581           ˇ
 7582           jkˇ
 7583           nlmo
 7584           "#
 7585    ));
 7586
 7587    cx.update_editor(|editor, window, cx| {
 7588        editor.add_selection_below(&Default::default(), window, cx);
 7589    });
 7590
 7591    cx.assert_editor_state(indoc!(
 7592        r#"abc
 7593           defˇghi
 7594           ˇ
 7595           jkˇ
 7596           nlmˇo
 7597           "#
 7598    ));
 7599
 7600    cx.update_editor(|editor, window, cx| {
 7601        editor.add_selection_below(&Default::default(), window, cx);
 7602    });
 7603
 7604    cx.assert_editor_state(indoc!(
 7605        r#"abc
 7606           defˇghi
 7607           ˇ
 7608           jkˇ
 7609           nlmˇo
 7610           ˇ"#
 7611    ));
 7612
 7613    // change selections
 7614    cx.set_state(indoc!(
 7615        r#"abc
 7616           def«ˇg»hi
 7617
 7618           jk
 7619           nlmo
 7620           "#
 7621    ));
 7622
 7623    cx.update_editor(|editor, window, cx| {
 7624        editor.add_selection_below(&Default::default(), window, cx);
 7625    });
 7626
 7627    cx.assert_editor_state(indoc!(
 7628        r#"abc
 7629           def«ˇg»hi
 7630
 7631           jk
 7632           nlm«ˇo»
 7633           "#
 7634    ));
 7635
 7636    cx.update_editor(|editor, window, cx| {
 7637        editor.add_selection_below(&Default::default(), window, cx);
 7638    });
 7639
 7640    cx.assert_editor_state(indoc!(
 7641        r#"abc
 7642           def«ˇg»hi
 7643
 7644           jk
 7645           nlm«ˇo»
 7646           "#
 7647    ));
 7648
 7649    cx.update_editor(|editor, window, cx| {
 7650        editor.add_selection_above(&Default::default(), window, cx);
 7651    });
 7652
 7653    cx.assert_editor_state(indoc!(
 7654        r#"abc
 7655           def«ˇg»hi
 7656
 7657           jk
 7658           nlmo
 7659           "#
 7660    ));
 7661
 7662    cx.update_editor(|editor, window, cx| {
 7663        editor.add_selection_above(&Default::default(), window, cx);
 7664    });
 7665
 7666    cx.assert_editor_state(indoc!(
 7667        r#"abc
 7668           def«ˇg»hi
 7669
 7670           jk
 7671           nlmo
 7672           "#
 7673    ));
 7674
 7675    // Change selections again
 7676    cx.set_state(indoc!(
 7677        r#"a«bc
 7678           defgˇ»hi
 7679
 7680           jk
 7681           nlmo
 7682           "#
 7683    ));
 7684
 7685    cx.update_editor(|editor, window, cx| {
 7686        editor.add_selection_below(&Default::default(), window, cx);
 7687    });
 7688
 7689    cx.assert_editor_state(indoc!(
 7690        r#"a«bcˇ»
 7691           d«efgˇ»hi
 7692
 7693           j«kˇ»
 7694           nlmo
 7695           "#
 7696    ));
 7697
 7698    cx.update_editor(|editor, window, cx| {
 7699        editor.add_selection_below(&Default::default(), window, cx);
 7700    });
 7701    cx.assert_editor_state(indoc!(
 7702        r#"a«bcˇ»
 7703           d«efgˇ»hi
 7704
 7705           j«kˇ»
 7706           n«lmoˇ»
 7707           "#
 7708    ));
 7709    cx.update_editor(|editor, window, cx| {
 7710        editor.add_selection_above(&Default::default(), window, cx);
 7711    });
 7712
 7713    cx.assert_editor_state(indoc!(
 7714        r#"a«bcˇ»
 7715           d«efgˇ»hi
 7716
 7717           j«kˇ»
 7718           nlmo
 7719           "#
 7720    ));
 7721
 7722    // Change selections again
 7723    cx.set_state(indoc!(
 7724        r#"abc
 7725           d«ˇefghi
 7726
 7727           jk
 7728           nlm»o
 7729           "#
 7730    ));
 7731
 7732    cx.update_editor(|editor, window, cx| {
 7733        editor.add_selection_above(&Default::default(), window, cx);
 7734    });
 7735
 7736    cx.assert_editor_state(indoc!(
 7737        r#"a«ˇbc»
 7738           d«ˇef»ghi
 7739
 7740           j«ˇk»
 7741           n«ˇlm»o
 7742           "#
 7743    ));
 7744
 7745    cx.update_editor(|editor, window, cx| {
 7746        editor.add_selection_below(&Default::default(), window, cx);
 7747    });
 7748
 7749    cx.assert_editor_state(indoc!(
 7750        r#"abc
 7751           d«ˇef»ghi
 7752
 7753           j«ˇk»
 7754           n«ˇlm»o
 7755           "#
 7756    ));
 7757}
 7758
 7759#[gpui::test]
 7760async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 7761    init_test(cx, |_| {});
 7762    let mut cx = EditorTestContext::new(cx).await;
 7763
 7764    cx.set_state(indoc!(
 7765        r#"line onˇe
 7766           liˇne two
 7767           line three
 7768           line four"#
 7769    ));
 7770
 7771    cx.update_editor(|editor, window, cx| {
 7772        editor.add_selection_below(&Default::default(), window, cx);
 7773    });
 7774
 7775    // test multiple cursors expand in the same direction
 7776    cx.assert_editor_state(indoc!(
 7777        r#"line onˇe
 7778           liˇne twˇo
 7779           liˇne three
 7780           line four"#
 7781    ));
 7782
 7783    cx.update_editor(|editor, window, cx| {
 7784        editor.add_selection_below(&Default::default(), window, cx);
 7785    });
 7786
 7787    cx.update_editor(|editor, window, cx| {
 7788        editor.add_selection_below(&Default::default(), window, cx);
 7789    });
 7790
 7791    // test multiple cursors expand below overflow
 7792    cx.assert_editor_state(indoc!(
 7793        r#"line onˇe
 7794           liˇne twˇo
 7795           liˇne thˇree
 7796           liˇne foˇur"#
 7797    ));
 7798
 7799    cx.update_editor(|editor, window, cx| {
 7800        editor.add_selection_above(&Default::default(), window, cx);
 7801    });
 7802
 7803    // test multiple cursors retrieves back correctly
 7804    cx.assert_editor_state(indoc!(
 7805        r#"line onˇe
 7806           liˇne twˇo
 7807           liˇne thˇree
 7808           line four"#
 7809    ));
 7810
 7811    cx.update_editor(|editor, window, cx| {
 7812        editor.add_selection_above(&Default::default(), window, cx);
 7813    });
 7814
 7815    cx.update_editor(|editor, window, cx| {
 7816        editor.add_selection_above(&Default::default(), window, cx);
 7817    });
 7818
 7819    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 7820    cx.assert_editor_state(indoc!(
 7821        r#"liˇne onˇe
 7822           liˇne two
 7823           line three
 7824           line four"#
 7825    ));
 7826
 7827    cx.update_editor(|editor, window, cx| {
 7828        editor.undo_selection(&Default::default(), window, cx);
 7829    });
 7830
 7831    // test undo
 7832    cx.assert_editor_state(indoc!(
 7833        r#"line onˇe
 7834           liˇne twˇo
 7835           line three
 7836           line four"#
 7837    ));
 7838
 7839    cx.update_editor(|editor, window, cx| {
 7840        editor.redo_selection(&Default::default(), window, cx);
 7841    });
 7842
 7843    // test redo
 7844    cx.assert_editor_state(indoc!(
 7845        r#"liˇne onˇe
 7846           liˇne two
 7847           line three
 7848           line four"#
 7849    ));
 7850
 7851    cx.set_state(indoc!(
 7852        r#"abcd
 7853           ef«ghˇ»
 7854           ijkl
 7855           «mˇ»nop"#
 7856    ));
 7857
 7858    cx.update_editor(|editor, window, cx| {
 7859        editor.add_selection_above(&Default::default(), window, cx);
 7860    });
 7861
 7862    // test multiple selections expand in the same direction
 7863    cx.assert_editor_state(indoc!(
 7864        r#"ab«cdˇ»
 7865           ef«ghˇ»
 7866           «iˇ»jkl
 7867           «mˇ»nop"#
 7868    ));
 7869
 7870    cx.update_editor(|editor, window, cx| {
 7871        editor.add_selection_above(&Default::default(), window, cx);
 7872    });
 7873
 7874    // test multiple selection upward overflow
 7875    cx.assert_editor_state(indoc!(
 7876        r#"ab«cdˇ»
 7877           «eˇ»f«ghˇ»
 7878           «iˇ»jkl
 7879           «mˇ»nop"#
 7880    ));
 7881
 7882    cx.update_editor(|editor, window, cx| {
 7883        editor.add_selection_below(&Default::default(), window, cx);
 7884    });
 7885
 7886    // test multiple selection retrieves back correctly
 7887    cx.assert_editor_state(indoc!(
 7888        r#"abcd
 7889           ef«ghˇ»
 7890           «iˇ»jkl
 7891           «mˇ»nop"#
 7892    ));
 7893
 7894    cx.update_editor(|editor, window, cx| {
 7895        editor.add_selection_below(&Default::default(), window, cx);
 7896    });
 7897
 7898    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 7899    cx.assert_editor_state(indoc!(
 7900        r#"abcd
 7901           ef«ghˇ»
 7902           ij«klˇ»
 7903           «mˇ»nop"#
 7904    ));
 7905
 7906    cx.update_editor(|editor, window, cx| {
 7907        editor.undo_selection(&Default::default(), window, cx);
 7908    });
 7909
 7910    // test undo
 7911    cx.assert_editor_state(indoc!(
 7912        r#"abcd
 7913           ef«ghˇ»
 7914           «iˇ»jkl
 7915           «mˇ»nop"#
 7916    ));
 7917
 7918    cx.update_editor(|editor, window, cx| {
 7919        editor.redo_selection(&Default::default(), window, cx);
 7920    });
 7921
 7922    // test redo
 7923    cx.assert_editor_state(indoc!(
 7924        r#"abcd
 7925           ef«ghˇ»
 7926           ij«klˇ»
 7927           «mˇ»nop"#
 7928    ));
 7929}
 7930
 7931#[gpui::test]
 7932async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 7933    init_test(cx, |_| {});
 7934    let mut cx = EditorTestContext::new(cx).await;
 7935
 7936    cx.set_state(indoc!(
 7937        r#"line onˇe
 7938           liˇne two
 7939           line three
 7940           line four"#
 7941    ));
 7942
 7943    cx.update_editor(|editor, window, cx| {
 7944        editor.add_selection_below(&Default::default(), window, cx);
 7945        editor.add_selection_below(&Default::default(), window, cx);
 7946        editor.add_selection_below(&Default::default(), window, cx);
 7947    });
 7948
 7949    // initial state with two multi cursor groups
 7950    cx.assert_editor_state(indoc!(
 7951        r#"line onˇe
 7952           liˇne twˇo
 7953           liˇne thˇree
 7954           liˇne foˇur"#
 7955    ));
 7956
 7957    // add single cursor in middle - simulate opt click
 7958    cx.update_editor(|editor, window, cx| {
 7959        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 7960        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 7961        editor.end_selection(window, cx);
 7962    });
 7963
 7964    cx.assert_editor_state(indoc!(
 7965        r#"line onˇe
 7966           liˇne twˇo
 7967           liˇneˇ thˇree
 7968           liˇne foˇur"#
 7969    ));
 7970
 7971    cx.update_editor(|editor, window, cx| {
 7972        editor.add_selection_above(&Default::default(), window, cx);
 7973    });
 7974
 7975    // test new added selection expands above and existing selection shrinks
 7976    cx.assert_editor_state(indoc!(
 7977        r#"line onˇe
 7978           liˇneˇ twˇo
 7979           liˇneˇ thˇree
 7980           line four"#
 7981    ));
 7982
 7983    cx.update_editor(|editor, window, cx| {
 7984        editor.add_selection_above(&Default::default(), window, cx);
 7985    });
 7986
 7987    // test new added selection expands above and existing selection shrinks
 7988    cx.assert_editor_state(indoc!(
 7989        r#"lineˇ onˇe
 7990           liˇneˇ twˇo
 7991           lineˇ three
 7992           line four"#
 7993    ));
 7994
 7995    // intial state with two selection groups
 7996    cx.set_state(indoc!(
 7997        r#"abcd
 7998           ef«ghˇ»
 7999           ijkl
 8000           «mˇ»nop"#
 8001    ));
 8002
 8003    cx.update_editor(|editor, window, cx| {
 8004        editor.add_selection_above(&Default::default(), window, cx);
 8005        editor.add_selection_above(&Default::default(), window, cx);
 8006    });
 8007
 8008    cx.assert_editor_state(indoc!(
 8009        r#"ab«cdˇ»
 8010           «eˇ»f«ghˇ»
 8011           «iˇ»jkl
 8012           «mˇ»nop"#
 8013    ));
 8014
 8015    // add single selection in middle - simulate opt drag
 8016    cx.update_editor(|editor, window, cx| {
 8017        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8018        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8019        editor.update_selection(
 8020            DisplayPoint::new(DisplayRow(2), 4),
 8021            0,
 8022            gpui::Point::<f32>::default(),
 8023            window,
 8024            cx,
 8025        );
 8026        editor.end_selection(window, cx);
 8027    });
 8028
 8029    cx.assert_editor_state(indoc!(
 8030        r#"ab«cdˇ»
 8031           «eˇ»f«ghˇ»
 8032           «iˇ»jk«lˇ»
 8033           «mˇ»nop"#
 8034    ));
 8035
 8036    cx.update_editor(|editor, window, cx| {
 8037        editor.add_selection_below(&Default::default(), window, cx);
 8038    });
 8039
 8040    // test new added selection expands below, others shrinks from above
 8041    cx.assert_editor_state(indoc!(
 8042        r#"abcd
 8043           ef«ghˇ»
 8044           «iˇ»jk«lˇ»
 8045           «mˇ»no«pˇ»"#
 8046    ));
 8047}
 8048
 8049#[gpui::test]
 8050async fn test_select_next(cx: &mut TestAppContext) {
 8051    init_test(cx, |_| {});
 8052
 8053    let mut cx = EditorTestContext::new(cx).await;
 8054    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8055
 8056    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8057        .unwrap();
 8058    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8059
 8060    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8061        .unwrap();
 8062    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8063
 8064    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8065    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8066
 8067    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8068    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8069
 8070    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8071        .unwrap();
 8072    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8073
 8074    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8075        .unwrap();
 8076    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8077
 8078    // Test selection direction should be preserved
 8079    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8080
 8081    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8082        .unwrap();
 8083    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8084}
 8085
 8086#[gpui::test]
 8087async fn test_select_all_matches(cx: &mut TestAppContext) {
 8088    init_test(cx, |_| {});
 8089
 8090    let mut cx = EditorTestContext::new(cx).await;
 8091
 8092    // Test caret-only selections
 8093    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8094    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8095        .unwrap();
 8096    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8097
 8098    // Test left-to-right selections
 8099    cx.set_state("abc\n«abcˇ»\nabc");
 8100    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8101        .unwrap();
 8102    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8103
 8104    // Test right-to-left selections
 8105    cx.set_state("abc\n«ˇabc»\nabc");
 8106    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8107        .unwrap();
 8108    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8109
 8110    // Test selecting whitespace with caret selection
 8111    cx.set_state("abc\nˇ   abc\nabc");
 8112    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8113        .unwrap();
 8114    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8115
 8116    // Test selecting whitespace with left-to-right selection
 8117    cx.set_state("abc\n«ˇ  »abc\nabc");
 8118    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8119        .unwrap();
 8120    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8121
 8122    // Test no matches with right-to-left selection
 8123    cx.set_state("abc\n«  ˇ»abc\nabc");
 8124    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8125        .unwrap();
 8126    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8127
 8128    // Test with a single word and clip_at_line_ends=true (#29823)
 8129    cx.set_state("aˇbc");
 8130    cx.update_editor(|e, window, cx| {
 8131        e.set_clip_at_line_ends(true, cx);
 8132        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8133        e.set_clip_at_line_ends(false, cx);
 8134    });
 8135    cx.assert_editor_state("«abcˇ»");
 8136}
 8137
 8138#[gpui::test]
 8139async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8140    init_test(cx, |_| {});
 8141
 8142    let mut cx = EditorTestContext::new(cx).await;
 8143
 8144    let large_body_1 = "\nd".repeat(200);
 8145    let large_body_2 = "\ne".repeat(200);
 8146
 8147    cx.set_state(&format!(
 8148        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8149    ));
 8150    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8151        let scroll_position = editor.scroll_position(cx);
 8152        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8153        scroll_position
 8154    });
 8155
 8156    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8157        .unwrap();
 8158    cx.assert_editor_state(&format!(
 8159        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8160    ));
 8161    let scroll_position_after_selection =
 8162        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8163    assert_eq!(
 8164        initial_scroll_position, scroll_position_after_selection,
 8165        "Scroll position should not change after selecting all matches"
 8166    );
 8167}
 8168
 8169#[gpui::test]
 8170async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8171    init_test(cx, |_| {});
 8172
 8173    let mut cx = EditorLspTestContext::new_rust(
 8174        lsp::ServerCapabilities {
 8175            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8176            ..Default::default()
 8177        },
 8178        cx,
 8179    )
 8180    .await;
 8181
 8182    cx.set_state(indoc! {"
 8183        line 1
 8184        line 2
 8185        linˇe 3
 8186        line 4
 8187        line 5
 8188    "});
 8189
 8190    // Make an edit
 8191    cx.update_editor(|editor, window, cx| {
 8192        editor.handle_input("X", window, cx);
 8193    });
 8194
 8195    // Move cursor to a different position
 8196    cx.update_editor(|editor, window, cx| {
 8197        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8198            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8199        });
 8200    });
 8201
 8202    cx.assert_editor_state(indoc! {"
 8203        line 1
 8204        line 2
 8205        linXe 3
 8206        line 4
 8207        liˇne 5
 8208    "});
 8209
 8210    cx.lsp
 8211        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8212            Ok(Some(vec![lsp::TextEdit::new(
 8213                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8214                "PREFIX ".to_string(),
 8215            )]))
 8216        });
 8217
 8218    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8219        .unwrap()
 8220        .await
 8221        .unwrap();
 8222
 8223    cx.assert_editor_state(indoc! {"
 8224        PREFIX line 1
 8225        line 2
 8226        linXe 3
 8227        line 4
 8228        liˇne 5
 8229    "});
 8230
 8231    // Undo formatting
 8232    cx.update_editor(|editor, window, cx| {
 8233        editor.undo(&Default::default(), window, cx);
 8234    });
 8235
 8236    // Verify cursor moved back to position after edit
 8237    cx.assert_editor_state(indoc! {"
 8238        line 1
 8239        line 2
 8240        linXˇe 3
 8241        line 4
 8242        line 5
 8243    "});
 8244}
 8245
 8246#[gpui::test]
 8247async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8248    init_test(cx, |_| {});
 8249
 8250    let mut cx = EditorTestContext::new(cx).await;
 8251
 8252    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 8253    cx.update_editor(|editor, window, cx| {
 8254        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8255    });
 8256
 8257    cx.set_state(indoc! {"
 8258        line 1
 8259        line 2
 8260        linˇe 3
 8261        line 4
 8262        line 5
 8263        line 6
 8264        line 7
 8265        line 8
 8266        line 9
 8267        line 10
 8268    "});
 8269
 8270    let snapshot = cx.buffer_snapshot();
 8271    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8272
 8273    cx.update(|_, cx| {
 8274        provider.update(cx, |provider, _| {
 8275            provider.set_edit_prediction(Some(edit_prediction::EditPrediction::Local {
 8276                id: None,
 8277                edits: vec![(edit_position..edit_position, "X".into())],
 8278                edit_preview: None,
 8279            }))
 8280        })
 8281    });
 8282
 8283    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8284    cx.update_editor(|editor, window, cx| {
 8285        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8286    });
 8287
 8288    cx.assert_editor_state(indoc! {"
 8289        line 1
 8290        line 2
 8291        lineXˇ 3
 8292        line 4
 8293        line 5
 8294        line 6
 8295        line 7
 8296        line 8
 8297        line 9
 8298        line 10
 8299    "});
 8300
 8301    cx.update_editor(|editor, window, cx| {
 8302        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8303            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8304        });
 8305    });
 8306
 8307    cx.assert_editor_state(indoc! {"
 8308        line 1
 8309        line 2
 8310        lineX 3
 8311        line 4
 8312        line 5
 8313        line 6
 8314        line 7
 8315        line 8
 8316        line 9
 8317        liˇne 10
 8318    "});
 8319
 8320    cx.update_editor(|editor, window, cx| {
 8321        editor.undo(&Default::default(), window, cx);
 8322    });
 8323
 8324    cx.assert_editor_state(indoc! {"
 8325        line 1
 8326        line 2
 8327        lineˇ 3
 8328        line 4
 8329        line 5
 8330        line 6
 8331        line 7
 8332        line 8
 8333        line 9
 8334        line 10
 8335    "});
 8336}
 8337
 8338#[gpui::test]
 8339async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8340    init_test(cx, |_| {});
 8341
 8342    let mut cx = EditorTestContext::new(cx).await;
 8343    cx.set_state(
 8344        r#"let foo = 2;
 8345lˇet foo = 2;
 8346let fooˇ = 2;
 8347let foo = 2;
 8348let foo = ˇ2;"#,
 8349    );
 8350
 8351    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8352        .unwrap();
 8353    cx.assert_editor_state(
 8354        r#"let foo = 2;
 8355«letˇ» foo = 2;
 8356let «fooˇ» = 2;
 8357let foo = 2;
 8358let foo = «2ˇ»;"#,
 8359    );
 8360
 8361    // noop for multiple selections with different contents
 8362    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8363        .unwrap();
 8364    cx.assert_editor_state(
 8365        r#"let foo = 2;
 8366«letˇ» foo = 2;
 8367let «fooˇ» = 2;
 8368let foo = 2;
 8369let foo = «2ˇ»;"#,
 8370    );
 8371
 8372    // Test last selection direction should be preserved
 8373    cx.set_state(
 8374        r#"let foo = 2;
 8375let foo = 2;
 8376let «fooˇ» = 2;
 8377let «ˇfoo» = 2;
 8378let foo = 2;"#,
 8379    );
 8380
 8381    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8382        .unwrap();
 8383    cx.assert_editor_state(
 8384        r#"let foo = 2;
 8385let foo = 2;
 8386let «fooˇ» = 2;
 8387let «ˇfoo» = 2;
 8388let «ˇfoo» = 2;"#,
 8389    );
 8390}
 8391
 8392#[gpui::test]
 8393async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8394    init_test(cx, |_| {});
 8395
 8396    let mut cx =
 8397        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8398
 8399    cx.assert_editor_state(indoc! {"
 8400        ˇbbb
 8401        ccc
 8402
 8403        bbb
 8404        ccc
 8405        "});
 8406    cx.dispatch_action(SelectPrevious::default());
 8407    cx.assert_editor_state(indoc! {"
 8408                «bbbˇ»
 8409                ccc
 8410
 8411                bbb
 8412                ccc
 8413                "});
 8414    cx.dispatch_action(SelectPrevious::default());
 8415    cx.assert_editor_state(indoc! {"
 8416                «bbbˇ»
 8417                ccc
 8418
 8419                «bbbˇ»
 8420                ccc
 8421                "});
 8422}
 8423
 8424#[gpui::test]
 8425async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8426    init_test(cx, |_| {});
 8427
 8428    let mut cx = EditorTestContext::new(cx).await;
 8429    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8430
 8431    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8432        .unwrap();
 8433    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8434
 8435    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8436        .unwrap();
 8437    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8438
 8439    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8440    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8441
 8442    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8443    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8444
 8445    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8446        .unwrap();
 8447    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 8448
 8449    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8450        .unwrap();
 8451    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8452}
 8453
 8454#[gpui::test]
 8455async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8456    init_test(cx, |_| {});
 8457
 8458    let mut cx = EditorTestContext::new(cx).await;
 8459    cx.set_state("");
 8460
 8461    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8462        .unwrap();
 8463    cx.assert_editor_state("«aˇ»");
 8464    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8465        .unwrap();
 8466    cx.assert_editor_state("«aˇ»");
 8467}
 8468
 8469#[gpui::test]
 8470async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8471    init_test(cx, |_| {});
 8472
 8473    let mut cx = EditorTestContext::new(cx).await;
 8474    cx.set_state(
 8475        r#"let foo = 2;
 8476lˇet foo = 2;
 8477let fooˇ = 2;
 8478let foo = 2;
 8479let foo = ˇ2;"#,
 8480    );
 8481
 8482    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8483        .unwrap();
 8484    cx.assert_editor_state(
 8485        r#"let foo = 2;
 8486«letˇ» foo = 2;
 8487let «fooˇ» = 2;
 8488let foo = 2;
 8489let foo = «2ˇ»;"#,
 8490    );
 8491
 8492    // noop for multiple selections with different contents
 8493    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8494        .unwrap();
 8495    cx.assert_editor_state(
 8496        r#"let foo = 2;
 8497«letˇ» foo = 2;
 8498let «fooˇ» = 2;
 8499let foo = 2;
 8500let foo = «2ˇ»;"#,
 8501    );
 8502}
 8503
 8504#[gpui::test]
 8505async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 8506    init_test(cx, |_| {});
 8507
 8508    let mut cx = EditorTestContext::new(cx).await;
 8509    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8510
 8511    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8512        .unwrap();
 8513    // selection direction is preserved
 8514    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8515
 8516    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8517        .unwrap();
 8518    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8519
 8520    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8521    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8522
 8523    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8524    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8525
 8526    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8527        .unwrap();
 8528    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 8529
 8530    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8531        .unwrap();
 8532    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 8533}
 8534
 8535#[gpui::test]
 8536async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 8537    init_test(cx, |_| {});
 8538
 8539    let language = Arc::new(Language::new(
 8540        LanguageConfig::default(),
 8541        Some(tree_sitter_rust::LANGUAGE.into()),
 8542    ));
 8543
 8544    let text = r#"
 8545        use mod1::mod2::{mod3, mod4};
 8546
 8547        fn fn_1(param1: bool, param2: &str) {
 8548            let var1 = "text";
 8549        }
 8550    "#
 8551    .unindent();
 8552
 8553    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8554    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8555    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8556
 8557    editor
 8558        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8559        .await;
 8560
 8561    editor.update_in(cx, |editor, window, cx| {
 8562        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8563            s.select_display_ranges([
 8564                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 8565                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 8566                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 8567            ]);
 8568        });
 8569        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8570    });
 8571    editor.update(cx, |editor, cx| {
 8572        assert_text_with_selections(
 8573            editor,
 8574            indoc! {r#"
 8575                use mod1::mod2::{mod3, «mod4ˇ»};
 8576
 8577                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8578                    let var1 = "«ˇtext»";
 8579                }
 8580            "#},
 8581            cx,
 8582        );
 8583    });
 8584
 8585    editor.update_in(cx, |editor, window, cx| {
 8586        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8587    });
 8588    editor.update(cx, |editor, cx| {
 8589        assert_text_with_selections(
 8590            editor,
 8591            indoc! {r#"
 8592                use mod1::mod2::«{mod3, mod4}ˇ»;
 8593
 8594                «ˇfn fn_1(param1: bool, param2: &str) {
 8595                    let var1 = "text";
 8596 8597            "#},
 8598            cx,
 8599        );
 8600    });
 8601
 8602    editor.update_in(cx, |editor, window, cx| {
 8603        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8604    });
 8605    assert_eq!(
 8606        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8607        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8608    );
 8609
 8610    // Trying to expand the selected syntax node one more time has no effect.
 8611    editor.update_in(cx, |editor, window, cx| {
 8612        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8613    });
 8614    assert_eq!(
 8615        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8616        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8617    );
 8618
 8619    editor.update_in(cx, |editor, window, cx| {
 8620        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8621    });
 8622    editor.update(cx, |editor, cx| {
 8623        assert_text_with_selections(
 8624            editor,
 8625            indoc! {r#"
 8626                use mod1::mod2::«{mod3, mod4}ˇ»;
 8627
 8628                «ˇfn fn_1(param1: bool, param2: &str) {
 8629                    let var1 = "text";
 8630 8631            "#},
 8632            cx,
 8633        );
 8634    });
 8635
 8636    editor.update_in(cx, |editor, window, cx| {
 8637        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8638    });
 8639    editor.update(cx, |editor, cx| {
 8640        assert_text_with_selections(
 8641            editor,
 8642            indoc! {r#"
 8643                use mod1::mod2::{mod3, «mod4ˇ»};
 8644
 8645                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8646                    let var1 = "«ˇtext»";
 8647                }
 8648            "#},
 8649            cx,
 8650        );
 8651    });
 8652
 8653    editor.update_in(cx, |editor, window, cx| {
 8654        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8655    });
 8656    editor.update(cx, |editor, cx| {
 8657        assert_text_with_selections(
 8658            editor,
 8659            indoc! {r#"
 8660                use mod1::mod2::{mod3, moˇd4};
 8661
 8662                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8663                    let var1 = "teˇxt";
 8664                }
 8665            "#},
 8666            cx,
 8667        );
 8668    });
 8669
 8670    // Trying to shrink the selected syntax node one more time has no effect.
 8671    editor.update_in(cx, |editor, window, cx| {
 8672        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8673    });
 8674    editor.update_in(cx, |editor, _, cx| {
 8675        assert_text_with_selections(
 8676            editor,
 8677            indoc! {r#"
 8678                use mod1::mod2::{mod3, moˇd4};
 8679
 8680                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8681                    let var1 = "teˇxt";
 8682                }
 8683            "#},
 8684            cx,
 8685        );
 8686    });
 8687
 8688    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 8689    // a fold.
 8690    editor.update_in(cx, |editor, window, cx| {
 8691        editor.fold_creases(
 8692            vec![
 8693                Crease::simple(
 8694                    Point::new(0, 21)..Point::new(0, 24),
 8695                    FoldPlaceholder::test(),
 8696                ),
 8697                Crease::simple(
 8698                    Point::new(3, 20)..Point::new(3, 22),
 8699                    FoldPlaceholder::test(),
 8700                ),
 8701            ],
 8702            true,
 8703            window,
 8704            cx,
 8705        );
 8706        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8707    });
 8708    editor.update(cx, |editor, cx| {
 8709        assert_text_with_selections(
 8710            editor,
 8711            indoc! {r#"
 8712                use mod1::mod2::«{mod3, mod4}ˇ»;
 8713
 8714                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8715                    let var1 = "«ˇtext»";
 8716                }
 8717            "#},
 8718            cx,
 8719        );
 8720    });
 8721}
 8722
 8723#[gpui::test]
 8724async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 8725    init_test(cx, |_| {});
 8726
 8727    let language = Arc::new(Language::new(
 8728        LanguageConfig::default(),
 8729        Some(tree_sitter_rust::LANGUAGE.into()),
 8730    ));
 8731
 8732    let text = "let a = 2;";
 8733
 8734    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8735    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8736    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8737
 8738    editor
 8739        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8740        .await;
 8741
 8742    // Test case 1: Cursor at end of word
 8743    editor.update_in(cx, |editor, window, cx| {
 8744        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8745            s.select_display_ranges([
 8746                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 8747            ]);
 8748        });
 8749    });
 8750    editor.update(cx, |editor, cx| {
 8751        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 8752    });
 8753    editor.update_in(cx, |editor, window, cx| {
 8754        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8755    });
 8756    editor.update(cx, |editor, cx| {
 8757        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 8758    });
 8759    editor.update_in(cx, |editor, window, cx| {
 8760        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8761    });
 8762    editor.update(cx, |editor, cx| {
 8763        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 8764    });
 8765
 8766    // Test case 2: Cursor at end of statement
 8767    editor.update_in(cx, |editor, window, cx| {
 8768        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8769            s.select_display_ranges([
 8770                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 8771            ]);
 8772        });
 8773    });
 8774    editor.update(cx, |editor, cx| {
 8775        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 8776    });
 8777    editor.update_in(cx, |editor, window, cx| {
 8778        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8779    });
 8780    editor.update(cx, |editor, cx| {
 8781        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 8782    });
 8783}
 8784
 8785#[gpui::test]
 8786async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 8787    init_test(cx, |_| {});
 8788
 8789    let language = Arc::new(Language::new(
 8790        LanguageConfig {
 8791            name: "JavaScript".into(),
 8792            ..Default::default()
 8793        },
 8794        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8795    ));
 8796
 8797    let text = r#"
 8798        let a = {
 8799            key: "value",
 8800        };
 8801    "#
 8802    .unindent();
 8803
 8804    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8805    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8806    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8807
 8808    editor
 8809        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8810        .await;
 8811
 8812    // Test case 1: Cursor after '{'
 8813    editor.update_in(cx, |editor, window, cx| {
 8814        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8815            s.select_display_ranges([
 8816                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 8817            ]);
 8818        });
 8819    });
 8820    editor.update(cx, |editor, cx| {
 8821        assert_text_with_selections(
 8822            editor,
 8823            indoc! {r#"
 8824                let a = {ˇ
 8825                    key: "value",
 8826                };
 8827            "#},
 8828            cx,
 8829        );
 8830    });
 8831    editor.update_in(cx, |editor, window, cx| {
 8832        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8833    });
 8834    editor.update(cx, |editor, cx| {
 8835        assert_text_with_selections(
 8836            editor,
 8837            indoc! {r#"
 8838                let a = «ˇ{
 8839                    key: "value",
 8840                }»;
 8841            "#},
 8842            cx,
 8843        );
 8844    });
 8845
 8846    // Test case 2: Cursor after ':'
 8847    editor.update_in(cx, |editor, window, cx| {
 8848        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8849            s.select_display_ranges([
 8850                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 8851            ]);
 8852        });
 8853    });
 8854    editor.update(cx, |editor, cx| {
 8855        assert_text_with_selections(
 8856            editor,
 8857            indoc! {r#"
 8858                let a = {
 8859                    key:ˇ "value",
 8860                };
 8861            "#},
 8862            cx,
 8863        );
 8864    });
 8865    editor.update_in(cx, |editor, window, cx| {
 8866        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8867    });
 8868    editor.update(cx, |editor, cx| {
 8869        assert_text_with_selections(
 8870            editor,
 8871            indoc! {r#"
 8872                let a = {
 8873                    «ˇkey: "value"»,
 8874                };
 8875            "#},
 8876            cx,
 8877        );
 8878    });
 8879    editor.update_in(cx, |editor, window, cx| {
 8880        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8881    });
 8882    editor.update(cx, |editor, cx| {
 8883        assert_text_with_selections(
 8884            editor,
 8885            indoc! {r#"
 8886                let a = «ˇ{
 8887                    key: "value",
 8888                }»;
 8889            "#},
 8890            cx,
 8891        );
 8892    });
 8893
 8894    // Test case 3: Cursor after ','
 8895    editor.update_in(cx, |editor, window, cx| {
 8896        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8897            s.select_display_ranges([
 8898                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 8899            ]);
 8900        });
 8901    });
 8902    editor.update(cx, |editor, cx| {
 8903        assert_text_with_selections(
 8904            editor,
 8905            indoc! {r#"
 8906                let a = {
 8907                    key: "value",ˇ
 8908                };
 8909            "#},
 8910            cx,
 8911        );
 8912    });
 8913    editor.update_in(cx, |editor, window, cx| {
 8914        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8915    });
 8916    editor.update(cx, |editor, cx| {
 8917        assert_text_with_selections(
 8918            editor,
 8919            indoc! {r#"
 8920                let a = «ˇ{
 8921                    key: "value",
 8922                }»;
 8923            "#},
 8924            cx,
 8925        );
 8926    });
 8927
 8928    // Test case 4: Cursor after ';'
 8929    editor.update_in(cx, |editor, window, cx| {
 8930        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8931            s.select_display_ranges([
 8932                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 8933            ]);
 8934        });
 8935    });
 8936    editor.update(cx, |editor, cx| {
 8937        assert_text_with_selections(
 8938            editor,
 8939            indoc! {r#"
 8940                let a = {
 8941                    key: "value",
 8942                };ˇ
 8943            "#},
 8944            cx,
 8945        );
 8946    });
 8947    editor.update_in(cx, |editor, window, cx| {
 8948        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8949    });
 8950    editor.update(cx, |editor, cx| {
 8951        assert_text_with_selections(
 8952            editor,
 8953            indoc! {r#"
 8954                «ˇlet a = {
 8955                    key: "value",
 8956                };
 8957                »"#},
 8958            cx,
 8959        );
 8960    });
 8961}
 8962
 8963#[gpui::test]
 8964async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 8965    init_test(cx, |_| {});
 8966
 8967    let language = Arc::new(Language::new(
 8968        LanguageConfig::default(),
 8969        Some(tree_sitter_rust::LANGUAGE.into()),
 8970    ));
 8971
 8972    let text = r#"
 8973        use mod1::mod2::{mod3, mod4};
 8974
 8975        fn fn_1(param1: bool, param2: &str) {
 8976            let var1 = "hello world";
 8977        }
 8978    "#
 8979    .unindent();
 8980
 8981    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8982    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8983    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8984
 8985    editor
 8986        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8987        .await;
 8988
 8989    // Test 1: Cursor on a letter of a string word
 8990    editor.update_in(cx, |editor, window, cx| {
 8991        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8992            s.select_display_ranges([
 8993                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 8994            ]);
 8995        });
 8996    });
 8997    editor.update_in(cx, |editor, window, cx| {
 8998        assert_text_with_selections(
 8999            editor,
 9000            indoc! {r#"
 9001                use mod1::mod2::{mod3, mod4};
 9002
 9003                fn fn_1(param1: bool, param2: &str) {
 9004                    let var1 = "hˇello world";
 9005                }
 9006            "#},
 9007            cx,
 9008        );
 9009        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9010        assert_text_with_selections(
 9011            editor,
 9012            indoc! {r#"
 9013                use mod1::mod2::{mod3, mod4};
 9014
 9015                fn fn_1(param1: bool, param2: &str) {
 9016                    let var1 = "«ˇhello» world";
 9017                }
 9018            "#},
 9019            cx,
 9020        );
 9021    });
 9022
 9023    // Test 2: Partial selection within a word
 9024    editor.update_in(cx, |editor, window, cx| {
 9025        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9026            s.select_display_ranges([
 9027                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9028            ]);
 9029        });
 9030    });
 9031    editor.update_in(cx, |editor, window, cx| {
 9032        assert_text_with_selections(
 9033            editor,
 9034            indoc! {r#"
 9035                use mod1::mod2::{mod3, mod4};
 9036
 9037                fn fn_1(param1: bool, param2: &str) {
 9038                    let var1 = "h«elˇ»lo world";
 9039                }
 9040            "#},
 9041            cx,
 9042        );
 9043        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9044        assert_text_with_selections(
 9045            editor,
 9046            indoc! {r#"
 9047                use mod1::mod2::{mod3, mod4};
 9048
 9049                fn fn_1(param1: bool, param2: &str) {
 9050                    let var1 = "«ˇhello» world";
 9051                }
 9052            "#},
 9053            cx,
 9054        );
 9055    });
 9056
 9057    // Test 3: Complete word already selected
 9058    editor.update_in(cx, |editor, window, cx| {
 9059        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9060            s.select_display_ranges([
 9061                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9062            ]);
 9063        });
 9064    });
 9065    editor.update_in(cx, |editor, window, cx| {
 9066        assert_text_with_selections(
 9067            editor,
 9068            indoc! {r#"
 9069                use mod1::mod2::{mod3, mod4};
 9070
 9071                fn fn_1(param1: bool, param2: &str) {
 9072                    let var1 = "«helloˇ» world";
 9073                }
 9074            "#},
 9075            cx,
 9076        );
 9077        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9078        assert_text_with_selections(
 9079            editor,
 9080            indoc! {r#"
 9081                use mod1::mod2::{mod3, mod4};
 9082
 9083                fn fn_1(param1: bool, param2: &str) {
 9084                    let var1 = "«hello worldˇ»";
 9085                }
 9086            "#},
 9087            cx,
 9088        );
 9089    });
 9090
 9091    // Test 4: Selection spanning across words
 9092    editor.update_in(cx, |editor, window, cx| {
 9093        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9094            s.select_display_ranges([
 9095                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9096            ]);
 9097        });
 9098    });
 9099    editor.update_in(cx, |editor, window, cx| {
 9100        assert_text_with_selections(
 9101            editor,
 9102            indoc! {r#"
 9103                use mod1::mod2::{mod3, mod4};
 9104
 9105                fn fn_1(param1: bool, param2: &str) {
 9106                    let var1 = "hel«lo woˇ»rld";
 9107                }
 9108            "#},
 9109            cx,
 9110        );
 9111        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9112        assert_text_with_selections(
 9113            editor,
 9114            indoc! {r#"
 9115                use mod1::mod2::{mod3, mod4};
 9116
 9117                fn fn_1(param1: bool, param2: &str) {
 9118                    let var1 = "«ˇhello world»";
 9119                }
 9120            "#},
 9121            cx,
 9122        );
 9123    });
 9124
 9125    // Test 5: Expansion beyond string
 9126    editor.update_in(cx, |editor, window, cx| {
 9127        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9128        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9129        assert_text_with_selections(
 9130            editor,
 9131            indoc! {r#"
 9132                use mod1::mod2::{mod3, mod4};
 9133
 9134                fn fn_1(param1: bool, param2: &str) {
 9135                    «ˇlet var1 = "hello world";»
 9136                }
 9137            "#},
 9138            cx,
 9139        );
 9140    });
 9141}
 9142
 9143#[gpui::test]
 9144async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9145    init_test(cx, |_| {});
 9146
 9147    let mut cx = EditorTestContext::new(cx).await;
 9148
 9149    let language = Arc::new(Language::new(
 9150        LanguageConfig::default(),
 9151        Some(tree_sitter_rust::LANGUAGE.into()),
 9152    ));
 9153
 9154    cx.update_buffer(|buffer, cx| {
 9155        buffer.set_language(Some(language), cx);
 9156    });
 9157
 9158    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9159    cx.update_editor(|editor, window, cx| {
 9160        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9161    });
 9162
 9163    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9164
 9165    cx.set_state(indoc! { r#"fn a() {
 9166          // what
 9167          // a
 9168          // ˇlong
 9169          // method
 9170          // I
 9171          // sure
 9172          // hope
 9173          // it
 9174          // works
 9175    }"# });
 9176
 9177    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
 9178    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 9179    cx.update(|_, cx| {
 9180        multi_buffer.update(cx, |multi_buffer, cx| {
 9181            multi_buffer.set_excerpts_for_path(
 9182                PathKey::for_buffer(&buffer, cx),
 9183                buffer,
 9184                [Point::new(1, 0)..Point::new(1, 0)],
 9185                3,
 9186                cx,
 9187            );
 9188        });
 9189    });
 9190
 9191    let editor2 = cx.new_window_entity(|window, cx| {
 9192        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 9193    });
 9194
 9195    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
 9196    cx.update_editor(|editor, window, cx| {
 9197        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9198            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
 9199        })
 9200    });
 9201
 9202    cx.assert_editor_state(indoc! { "
 9203        fn a() {
 9204              // what
 9205              // a
 9206        ˇ      // long
 9207              // method"});
 9208
 9209    cx.update_editor(|editor, window, cx| {
 9210        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9211    });
 9212
 9213    // Although we could potentially make the action work when the syntax node
 9214    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
 9215    // did. Maybe we could also expand the excerpt to contain the range?
 9216    cx.assert_editor_state(indoc! { "
 9217        fn a() {
 9218              // what
 9219              // a
 9220        ˇ      // long
 9221              // method"});
 9222}
 9223
 9224#[gpui::test]
 9225async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9226    init_test(cx, |_| {});
 9227
 9228    let base_text = r#"
 9229        impl A {
 9230            // this is an uncommitted comment
 9231
 9232            fn b() {
 9233                c();
 9234            }
 9235
 9236            // this is another uncommitted comment
 9237
 9238            fn d() {
 9239                // e
 9240                // f
 9241            }
 9242        }
 9243
 9244        fn g() {
 9245            // h
 9246        }
 9247    "#
 9248    .unindent();
 9249
 9250    let text = r#"
 9251        ˇimpl A {
 9252
 9253            fn b() {
 9254                c();
 9255            }
 9256
 9257            fn d() {
 9258                // e
 9259                // f
 9260            }
 9261        }
 9262
 9263        fn g() {
 9264            // h
 9265        }
 9266    "#
 9267    .unindent();
 9268
 9269    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9270    cx.set_state(&text);
 9271    cx.set_head_text(&base_text);
 9272    cx.update_editor(|editor, window, cx| {
 9273        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9274    });
 9275
 9276    cx.assert_state_with_diff(
 9277        "
 9278        ˇimpl A {
 9279      -     // this is an uncommitted comment
 9280
 9281            fn b() {
 9282                c();
 9283            }
 9284
 9285      -     // this is another uncommitted comment
 9286      -
 9287            fn d() {
 9288                // e
 9289                // f
 9290            }
 9291        }
 9292
 9293        fn g() {
 9294            // h
 9295        }
 9296    "
 9297        .unindent(),
 9298    );
 9299
 9300    let expected_display_text = "
 9301        impl A {
 9302            // this is an uncommitted comment
 9303
 9304            fn b() {
 9305 9306            }
 9307
 9308            // this is another uncommitted comment
 9309
 9310            fn d() {
 9311 9312            }
 9313        }
 9314
 9315        fn g() {
 9316 9317        }
 9318        "
 9319    .unindent();
 9320
 9321    cx.update_editor(|editor, window, cx| {
 9322        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9323        assert_eq!(editor.display_text(cx), expected_display_text);
 9324    });
 9325}
 9326
 9327#[gpui::test]
 9328async fn test_autoindent(cx: &mut TestAppContext) {
 9329    init_test(cx, |_| {});
 9330
 9331    let language = Arc::new(
 9332        Language::new(
 9333            LanguageConfig {
 9334                brackets: BracketPairConfig {
 9335                    pairs: vec![
 9336                        BracketPair {
 9337                            start: "{".to_string(),
 9338                            end: "}".to_string(),
 9339                            close: false,
 9340                            surround: false,
 9341                            newline: true,
 9342                        },
 9343                        BracketPair {
 9344                            start: "(".to_string(),
 9345                            end: ")".to_string(),
 9346                            close: false,
 9347                            surround: false,
 9348                            newline: true,
 9349                        },
 9350                    ],
 9351                    ..Default::default()
 9352                },
 9353                ..Default::default()
 9354            },
 9355            Some(tree_sitter_rust::LANGUAGE.into()),
 9356        )
 9357        .with_indents_query(
 9358            r#"
 9359                (_ "(" ")" @end) @indent
 9360                (_ "{" "}" @end) @indent
 9361            "#,
 9362        )
 9363        .unwrap(),
 9364    );
 9365
 9366    let text = "fn a() {}";
 9367
 9368    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9369    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9370    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9371    editor
 9372        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9373        .await;
 9374
 9375    editor.update_in(cx, |editor, window, cx| {
 9376        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9377            s.select_ranges([5..5, 8..8, 9..9])
 9378        });
 9379        editor.newline(&Newline, window, cx);
 9380        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9381        assert_eq!(
 9382            editor.selections.ranges(cx),
 9383            &[
 9384                Point::new(1, 4)..Point::new(1, 4),
 9385                Point::new(3, 4)..Point::new(3, 4),
 9386                Point::new(5, 0)..Point::new(5, 0)
 9387            ]
 9388        );
 9389    });
 9390}
 9391
 9392#[gpui::test]
 9393async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9394    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9395
 9396    let language = Arc::new(
 9397        Language::new(
 9398            LanguageConfig {
 9399                brackets: BracketPairConfig {
 9400                    pairs: vec![
 9401                        BracketPair {
 9402                            start: "{".to_string(),
 9403                            end: "}".to_string(),
 9404                            close: false,
 9405                            surround: false,
 9406                            newline: true,
 9407                        },
 9408                        BracketPair {
 9409                            start: "(".to_string(),
 9410                            end: ")".to_string(),
 9411                            close: false,
 9412                            surround: false,
 9413                            newline: true,
 9414                        },
 9415                    ],
 9416                    ..Default::default()
 9417                },
 9418                ..Default::default()
 9419            },
 9420            Some(tree_sitter_rust::LANGUAGE.into()),
 9421        )
 9422        .with_indents_query(
 9423            r#"
 9424                (_ "(" ")" @end) @indent
 9425                (_ "{" "}" @end) @indent
 9426            "#,
 9427        )
 9428        .unwrap(),
 9429    );
 9430
 9431    let text = "fn a() {}";
 9432
 9433    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9434    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9435    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9436    editor
 9437        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9438        .await;
 9439
 9440    editor.update_in(cx, |editor, window, cx| {
 9441        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9442            s.select_ranges([5..5, 8..8, 9..9])
 9443        });
 9444        editor.newline(&Newline, window, cx);
 9445        assert_eq!(
 9446            editor.text(cx),
 9447            indoc!(
 9448                "
 9449                fn a(
 9450
 9451                ) {
 9452
 9453                }
 9454                "
 9455            )
 9456        );
 9457        assert_eq!(
 9458            editor.selections.ranges(cx),
 9459            &[
 9460                Point::new(1, 0)..Point::new(1, 0),
 9461                Point::new(3, 0)..Point::new(3, 0),
 9462                Point::new(5, 0)..Point::new(5, 0)
 9463            ]
 9464        );
 9465    });
 9466}
 9467
 9468#[gpui::test]
 9469async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 9470    init_test(cx, |settings| {
 9471        settings.defaults.auto_indent = Some(true);
 9472        settings.languages.0.insert(
 9473            "python".into(),
 9474            LanguageSettingsContent {
 9475                auto_indent: Some(false),
 9476                ..Default::default()
 9477            },
 9478        );
 9479    });
 9480
 9481    let mut cx = EditorTestContext::new(cx).await;
 9482
 9483    let injected_language = Arc::new(
 9484        Language::new(
 9485            LanguageConfig {
 9486                brackets: BracketPairConfig {
 9487                    pairs: vec![
 9488                        BracketPair {
 9489                            start: "{".to_string(),
 9490                            end: "}".to_string(),
 9491                            close: false,
 9492                            surround: false,
 9493                            newline: true,
 9494                        },
 9495                        BracketPair {
 9496                            start: "(".to_string(),
 9497                            end: ")".to_string(),
 9498                            close: true,
 9499                            surround: false,
 9500                            newline: true,
 9501                        },
 9502                    ],
 9503                    ..Default::default()
 9504                },
 9505                name: "python".into(),
 9506                ..Default::default()
 9507            },
 9508            Some(tree_sitter_python::LANGUAGE.into()),
 9509        )
 9510        .with_indents_query(
 9511            r#"
 9512                (_ "(" ")" @end) @indent
 9513                (_ "{" "}" @end) @indent
 9514            "#,
 9515        )
 9516        .unwrap(),
 9517    );
 9518
 9519    let language = Arc::new(
 9520        Language::new(
 9521            LanguageConfig {
 9522                brackets: BracketPairConfig {
 9523                    pairs: vec![
 9524                        BracketPair {
 9525                            start: "{".to_string(),
 9526                            end: "}".to_string(),
 9527                            close: false,
 9528                            surround: false,
 9529                            newline: true,
 9530                        },
 9531                        BracketPair {
 9532                            start: "(".to_string(),
 9533                            end: ")".to_string(),
 9534                            close: true,
 9535                            surround: false,
 9536                            newline: true,
 9537                        },
 9538                    ],
 9539                    ..Default::default()
 9540                },
 9541                name: LanguageName::new("rust"),
 9542                ..Default::default()
 9543            },
 9544            Some(tree_sitter_rust::LANGUAGE.into()),
 9545        )
 9546        .with_indents_query(
 9547            r#"
 9548                (_ "(" ")" @end) @indent
 9549                (_ "{" "}" @end) @indent
 9550            "#,
 9551        )
 9552        .unwrap()
 9553        .with_injection_query(
 9554            r#"
 9555            (macro_invocation
 9556                macro: (identifier) @_macro_name
 9557                (token_tree) @injection.content
 9558                (#set! injection.language "python"))
 9559           "#,
 9560        )
 9561        .unwrap(),
 9562    );
 9563
 9564    cx.language_registry().add(injected_language);
 9565    cx.language_registry().add(language.clone());
 9566
 9567    cx.update_buffer(|buffer, cx| {
 9568        buffer.set_language(Some(language), cx);
 9569    });
 9570
 9571    cx.set_state(r#"struct A {ˇ}"#);
 9572
 9573    cx.update_editor(|editor, window, cx| {
 9574        editor.newline(&Default::default(), window, cx);
 9575    });
 9576
 9577    cx.assert_editor_state(indoc!(
 9578        "struct A {
 9579            ˇ
 9580        }"
 9581    ));
 9582
 9583    cx.set_state(r#"select_biased!(ˇ)"#);
 9584
 9585    cx.update_editor(|editor, window, cx| {
 9586        editor.newline(&Default::default(), window, cx);
 9587        editor.handle_input("def ", window, cx);
 9588        editor.handle_input("(", window, cx);
 9589        editor.newline(&Default::default(), window, cx);
 9590        editor.handle_input("a", window, cx);
 9591    });
 9592
 9593    cx.assert_editor_state(indoc!(
 9594        "select_biased!(
 9595        def (
 9596 9597        )
 9598        )"
 9599    ));
 9600}
 9601
 9602#[gpui::test]
 9603async fn test_autoindent_selections(cx: &mut TestAppContext) {
 9604    init_test(cx, |_| {});
 9605
 9606    {
 9607        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9608        cx.set_state(indoc! {"
 9609            impl A {
 9610
 9611                fn b() {}
 9612
 9613            «fn c() {
 9614
 9615            }ˇ»
 9616            }
 9617        "});
 9618
 9619        cx.update_editor(|editor, window, cx| {
 9620            editor.autoindent(&Default::default(), window, cx);
 9621        });
 9622
 9623        cx.assert_editor_state(indoc! {"
 9624            impl A {
 9625
 9626                fn b() {}
 9627
 9628                «fn c() {
 9629
 9630                }ˇ»
 9631            }
 9632        "});
 9633    }
 9634
 9635    {
 9636        let mut cx = EditorTestContext::new_multibuffer(
 9637            cx,
 9638            [indoc! { "
 9639                impl A {
 9640                «
 9641                // a
 9642                fn b(){}
 9643                »
 9644                «
 9645                    }
 9646                    fn c(){}
 9647                »
 9648            "}],
 9649        );
 9650
 9651        let buffer = cx.update_editor(|editor, _, cx| {
 9652            let buffer = editor.buffer().update(cx, |buffer, _| {
 9653                buffer.all_buffers().iter().next().unwrap().clone()
 9654            });
 9655            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 9656            buffer
 9657        });
 9658
 9659        cx.run_until_parked();
 9660        cx.update_editor(|editor, window, cx| {
 9661            editor.select_all(&Default::default(), window, cx);
 9662            editor.autoindent(&Default::default(), window, cx)
 9663        });
 9664        cx.run_until_parked();
 9665
 9666        cx.update(|_, cx| {
 9667            assert_eq!(
 9668                buffer.read(cx).text(),
 9669                indoc! { "
 9670                    impl A {
 9671
 9672                        // a
 9673                        fn b(){}
 9674
 9675
 9676                    }
 9677                    fn c(){}
 9678
 9679                " }
 9680            )
 9681        });
 9682    }
 9683}
 9684
 9685#[gpui::test]
 9686async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 9687    init_test(cx, |_| {});
 9688
 9689    let mut cx = EditorTestContext::new(cx).await;
 9690
 9691    let language = Arc::new(Language::new(
 9692        LanguageConfig {
 9693            brackets: BracketPairConfig {
 9694                pairs: vec![
 9695                    BracketPair {
 9696                        start: "{".to_string(),
 9697                        end: "}".to_string(),
 9698                        close: true,
 9699                        surround: true,
 9700                        newline: true,
 9701                    },
 9702                    BracketPair {
 9703                        start: "(".to_string(),
 9704                        end: ")".to_string(),
 9705                        close: true,
 9706                        surround: true,
 9707                        newline: true,
 9708                    },
 9709                    BracketPair {
 9710                        start: "/*".to_string(),
 9711                        end: " */".to_string(),
 9712                        close: true,
 9713                        surround: true,
 9714                        newline: true,
 9715                    },
 9716                    BracketPair {
 9717                        start: "[".to_string(),
 9718                        end: "]".to_string(),
 9719                        close: false,
 9720                        surround: false,
 9721                        newline: true,
 9722                    },
 9723                    BracketPair {
 9724                        start: "\"".to_string(),
 9725                        end: "\"".to_string(),
 9726                        close: true,
 9727                        surround: true,
 9728                        newline: false,
 9729                    },
 9730                    BracketPair {
 9731                        start: "<".to_string(),
 9732                        end: ">".to_string(),
 9733                        close: false,
 9734                        surround: true,
 9735                        newline: true,
 9736                    },
 9737                ],
 9738                ..Default::default()
 9739            },
 9740            autoclose_before: "})]".to_string(),
 9741            ..Default::default()
 9742        },
 9743        Some(tree_sitter_rust::LANGUAGE.into()),
 9744    ));
 9745
 9746    cx.language_registry().add(language.clone());
 9747    cx.update_buffer(|buffer, cx| {
 9748        buffer.set_language(Some(language), cx);
 9749    });
 9750
 9751    cx.set_state(
 9752        &r#"
 9753            🏀ˇ
 9754            εˇ
 9755            ❤️ˇ
 9756        "#
 9757        .unindent(),
 9758    );
 9759
 9760    // autoclose multiple nested brackets at multiple cursors
 9761    cx.update_editor(|editor, window, cx| {
 9762        editor.handle_input("{", window, cx);
 9763        editor.handle_input("{", window, cx);
 9764        editor.handle_input("{", window, cx);
 9765    });
 9766    cx.assert_editor_state(
 9767        &"
 9768            🏀{{{ˇ}}}
 9769            ε{{{ˇ}}}
 9770            ❤️{{{ˇ}}}
 9771        "
 9772        .unindent(),
 9773    );
 9774
 9775    // insert a different closing bracket
 9776    cx.update_editor(|editor, window, cx| {
 9777        editor.handle_input(")", window, cx);
 9778    });
 9779    cx.assert_editor_state(
 9780        &"
 9781            🏀{{{)ˇ}}}
 9782            ε{{{)ˇ}}}
 9783            ❤️{{{)ˇ}}}
 9784        "
 9785        .unindent(),
 9786    );
 9787
 9788    // skip over the auto-closed brackets when typing a closing bracket
 9789    cx.update_editor(|editor, window, cx| {
 9790        editor.move_right(&MoveRight, window, cx);
 9791        editor.handle_input("}", window, cx);
 9792        editor.handle_input("}", window, cx);
 9793        editor.handle_input("}", window, cx);
 9794    });
 9795    cx.assert_editor_state(
 9796        &"
 9797            🏀{{{)}}}}ˇ
 9798            ε{{{)}}}}ˇ
 9799            ❤️{{{)}}}}ˇ
 9800        "
 9801        .unindent(),
 9802    );
 9803
 9804    // autoclose multi-character pairs
 9805    cx.set_state(
 9806        &"
 9807            ˇ
 9808            ˇ
 9809        "
 9810        .unindent(),
 9811    );
 9812    cx.update_editor(|editor, window, cx| {
 9813        editor.handle_input("/", window, cx);
 9814        editor.handle_input("*", window, cx);
 9815    });
 9816    cx.assert_editor_state(
 9817        &"
 9818            /*ˇ */
 9819            /*ˇ */
 9820        "
 9821        .unindent(),
 9822    );
 9823
 9824    // one cursor autocloses a multi-character pair, one cursor
 9825    // does not autoclose.
 9826    cx.set_state(
 9827        &"
 9828 9829            ˇ
 9830        "
 9831        .unindent(),
 9832    );
 9833    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 9834    cx.assert_editor_state(
 9835        &"
 9836            /*ˇ */
 9837 9838        "
 9839        .unindent(),
 9840    );
 9841
 9842    // Don't autoclose if the next character isn't whitespace and isn't
 9843    // listed in the language's "autoclose_before" section.
 9844    cx.set_state("ˇa b");
 9845    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9846    cx.assert_editor_state("{ˇa b");
 9847
 9848    // Don't autoclose if `close` is false for the bracket pair
 9849    cx.set_state("ˇ");
 9850    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 9851    cx.assert_editor_state("");
 9852
 9853    // Surround with brackets if text is selected
 9854    cx.set_state("«aˇ» b");
 9855    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9856    cx.assert_editor_state("{«aˇ»} b");
 9857
 9858    // Autoclose when not immediately after a word character
 9859    cx.set_state("a ˇ");
 9860    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9861    cx.assert_editor_state("a \"ˇ\"");
 9862
 9863    // Autoclose pair where the start and end characters are the same
 9864    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9865    cx.assert_editor_state("a \"\"ˇ");
 9866
 9867    // Don't autoclose when immediately after a word character
 9868    cx.set_state("");
 9869    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9870    cx.assert_editor_state("a\"ˇ");
 9871
 9872    // Do autoclose when after a non-word character
 9873    cx.set_state("");
 9874    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9875    cx.assert_editor_state("{\"ˇ\"");
 9876
 9877    // Non identical pairs autoclose regardless of preceding character
 9878    cx.set_state("");
 9879    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9880    cx.assert_editor_state("a{ˇ}");
 9881
 9882    // Don't autoclose pair if autoclose is disabled
 9883    cx.set_state("ˇ");
 9884    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9885    cx.assert_editor_state("");
 9886
 9887    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 9888    cx.set_state("«aˇ» b");
 9889    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9890    cx.assert_editor_state("<«aˇ»> b");
 9891}
 9892
 9893#[gpui::test]
 9894async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 9895    init_test(cx, |settings| {
 9896        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9897    });
 9898
 9899    let mut cx = EditorTestContext::new(cx).await;
 9900
 9901    let language = Arc::new(Language::new(
 9902        LanguageConfig {
 9903            brackets: BracketPairConfig {
 9904                pairs: vec![
 9905                    BracketPair {
 9906                        start: "{".to_string(),
 9907                        end: "}".to_string(),
 9908                        close: true,
 9909                        surround: true,
 9910                        newline: true,
 9911                    },
 9912                    BracketPair {
 9913                        start: "(".to_string(),
 9914                        end: ")".to_string(),
 9915                        close: true,
 9916                        surround: true,
 9917                        newline: true,
 9918                    },
 9919                    BracketPair {
 9920                        start: "[".to_string(),
 9921                        end: "]".to_string(),
 9922                        close: false,
 9923                        surround: false,
 9924                        newline: true,
 9925                    },
 9926                ],
 9927                ..Default::default()
 9928            },
 9929            autoclose_before: "})]".to_string(),
 9930            ..Default::default()
 9931        },
 9932        Some(tree_sitter_rust::LANGUAGE.into()),
 9933    ));
 9934
 9935    cx.language_registry().add(language.clone());
 9936    cx.update_buffer(|buffer, cx| {
 9937        buffer.set_language(Some(language), cx);
 9938    });
 9939
 9940    cx.set_state(
 9941        &"
 9942            ˇ
 9943            ˇ
 9944            ˇ
 9945        "
 9946        .unindent(),
 9947    );
 9948
 9949    // ensure only matching closing brackets are skipped over
 9950    cx.update_editor(|editor, window, cx| {
 9951        editor.handle_input("}", window, cx);
 9952        editor.move_left(&MoveLeft, window, cx);
 9953        editor.handle_input(")", window, cx);
 9954        editor.move_left(&MoveLeft, window, cx);
 9955    });
 9956    cx.assert_editor_state(
 9957        &"
 9958            ˇ)}
 9959            ˇ)}
 9960            ˇ)}
 9961        "
 9962        .unindent(),
 9963    );
 9964
 9965    // skip-over closing brackets at multiple cursors
 9966    cx.update_editor(|editor, window, cx| {
 9967        editor.handle_input(")", window, cx);
 9968        editor.handle_input("}", window, cx);
 9969    });
 9970    cx.assert_editor_state(
 9971        &"
 9972            )}ˇ
 9973            )}ˇ
 9974            )}ˇ
 9975        "
 9976        .unindent(),
 9977    );
 9978
 9979    // ignore non-close brackets
 9980    cx.update_editor(|editor, window, cx| {
 9981        editor.handle_input("]", window, cx);
 9982        editor.move_left(&MoveLeft, window, cx);
 9983        editor.handle_input("]", window, cx);
 9984    });
 9985    cx.assert_editor_state(
 9986        &"
 9987            )}]ˇ]
 9988            )}]ˇ]
 9989            )}]ˇ]
 9990        "
 9991        .unindent(),
 9992    );
 9993}
 9994
 9995#[gpui::test]
 9996async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 9997    init_test(cx, |_| {});
 9998
 9999    let mut cx = EditorTestContext::new(cx).await;
10000
10001    let html_language = Arc::new(
10002        Language::new(
10003            LanguageConfig {
10004                name: "HTML".into(),
10005                brackets: BracketPairConfig {
10006                    pairs: vec![
10007                        BracketPair {
10008                            start: "<".into(),
10009                            end: ">".into(),
10010                            close: true,
10011                            ..Default::default()
10012                        },
10013                        BracketPair {
10014                            start: "{".into(),
10015                            end: "}".into(),
10016                            close: true,
10017                            ..Default::default()
10018                        },
10019                        BracketPair {
10020                            start: "(".into(),
10021                            end: ")".into(),
10022                            close: true,
10023                            ..Default::default()
10024                        },
10025                    ],
10026                    ..Default::default()
10027                },
10028                autoclose_before: "})]>".into(),
10029                ..Default::default()
10030            },
10031            Some(tree_sitter_html::LANGUAGE.into()),
10032        )
10033        .with_injection_query(
10034            r#"
10035            (script_element
10036                (raw_text) @injection.content
10037                (#set! injection.language "javascript"))
10038            "#,
10039        )
10040        .unwrap(),
10041    );
10042
10043    let javascript_language = Arc::new(Language::new(
10044        LanguageConfig {
10045            name: "JavaScript".into(),
10046            brackets: BracketPairConfig {
10047                pairs: vec![
10048                    BracketPair {
10049                        start: "/*".into(),
10050                        end: " */".into(),
10051                        close: true,
10052                        ..Default::default()
10053                    },
10054                    BracketPair {
10055                        start: "{".into(),
10056                        end: "}".into(),
10057                        close: true,
10058                        ..Default::default()
10059                    },
10060                    BracketPair {
10061                        start: "(".into(),
10062                        end: ")".into(),
10063                        close: true,
10064                        ..Default::default()
10065                    },
10066                ],
10067                ..Default::default()
10068            },
10069            autoclose_before: "})]>".into(),
10070            ..Default::default()
10071        },
10072        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10073    ));
10074
10075    cx.language_registry().add(html_language.clone());
10076    cx.language_registry().add(javascript_language);
10077    cx.executor().run_until_parked();
10078
10079    cx.update_buffer(|buffer, cx| {
10080        buffer.set_language(Some(html_language), cx);
10081    });
10082
10083    cx.set_state(
10084        &r#"
10085            <body>ˇ
10086                <script>
10087                    var x = 1;ˇ
10088                </script>
10089            </body>ˇ
10090        "#
10091        .unindent(),
10092    );
10093
10094    // Precondition: different languages are active at different locations.
10095    cx.update_editor(|editor, window, cx| {
10096        let snapshot = editor.snapshot(window, cx);
10097        let cursors = editor.selections.ranges::<usize>(cx);
10098        let languages = cursors
10099            .iter()
10100            .map(|c| snapshot.language_at(c.start).unwrap().name())
10101            .collect::<Vec<_>>();
10102        assert_eq!(
10103            languages,
10104            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10105        );
10106    });
10107
10108    // Angle brackets autoclose in HTML, but not JavaScript.
10109    cx.update_editor(|editor, window, cx| {
10110        editor.handle_input("<", window, cx);
10111        editor.handle_input("a", window, cx);
10112    });
10113    cx.assert_editor_state(
10114        &r#"
10115            <body><aˇ>
10116                <script>
10117                    var x = 1;<aˇ
10118                </script>
10119            </body><aˇ>
10120        "#
10121        .unindent(),
10122    );
10123
10124    // Curly braces and parens autoclose in both HTML and JavaScript.
10125    cx.update_editor(|editor, window, cx| {
10126        editor.handle_input(" b=", window, cx);
10127        editor.handle_input("{", window, cx);
10128        editor.handle_input("c", window, cx);
10129        editor.handle_input("(", window, cx);
10130    });
10131    cx.assert_editor_state(
10132        &r#"
10133            <body><a b={c(ˇ)}>
10134                <script>
10135                    var x = 1;<a b={c(ˇ)}
10136                </script>
10137            </body><a b={c(ˇ)}>
10138        "#
10139        .unindent(),
10140    );
10141
10142    // Brackets that were already autoclosed are skipped.
10143    cx.update_editor(|editor, window, cx| {
10144        editor.handle_input(")", window, cx);
10145        editor.handle_input("d", window, cx);
10146        editor.handle_input("}", window, cx);
10147    });
10148    cx.assert_editor_state(
10149        &r#"
10150            <body><a b={c()d}ˇ>
10151                <script>
10152                    var x = 1;<a b={c()d}ˇ
10153                </script>
10154            </body><a b={c()d}ˇ>
10155        "#
10156        .unindent(),
10157    );
10158    cx.update_editor(|editor, window, cx| {
10159        editor.handle_input(">", window, cx);
10160    });
10161    cx.assert_editor_state(
10162        &r#"
10163            <body><a b={c()d}>ˇ
10164                <script>
10165                    var x = 1;<a b={c()d}>ˇ
10166                </script>
10167            </body><a b={c()d}>ˇ
10168        "#
10169        .unindent(),
10170    );
10171
10172    // Reset
10173    cx.set_state(
10174        &r#"
10175            <body>ˇ
10176                <script>
10177                    var x = 1;ˇ
10178                </script>
10179            </body>ˇ
10180        "#
10181        .unindent(),
10182    );
10183
10184    cx.update_editor(|editor, window, cx| {
10185        editor.handle_input("<", window, cx);
10186    });
10187    cx.assert_editor_state(
10188        &r#"
10189            <body><ˇ>
10190                <script>
10191                    var x = 1;<ˇ
10192                </script>
10193            </body><ˇ>
10194        "#
10195        .unindent(),
10196    );
10197
10198    // When backspacing, the closing angle brackets are removed.
10199    cx.update_editor(|editor, window, cx| {
10200        editor.backspace(&Backspace, window, cx);
10201    });
10202    cx.assert_editor_state(
10203        &r#"
10204            <body>ˇ
10205                <script>
10206                    var x = 1;ˇ
10207                </script>
10208            </body>ˇ
10209        "#
10210        .unindent(),
10211    );
10212
10213    // Block comments autoclose in JavaScript, but not HTML.
10214    cx.update_editor(|editor, window, cx| {
10215        editor.handle_input("/", window, cx);
10216        editor.handle_input("*", window, cx);
10217    });
10218    cx.assert_editor_state(
10219        &r#"
10220            <body>/*ˇ
10221                <script>
10222                    var x = 1;/*ˇ */
10223                </script>
10224            </body>/*ˇ
10225        "#
10226        .unindent(),
10227    );
10228}
10229
10230#[gpui::test]
10231async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10232    init_test(cx, |_| {});
10233
10234    let mut cx = EditorTestContext::new(cx).await;
10235
10236    let rust_language = Arc::new(
10237        Language::new(
10238            LanguageConfig {
10239                name: "Rust".into(),
10240                brackets: serde_json::from_value(json!([
10241                    { "start": "{", "end": "}", "close": true, "newline": true },
10242                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10243                ]))
10244                .unwrap(),
10245                autoclose_before: "})]>".into(),
10246                ..Default::default()
10247            },
10248            Some(tree_sitter_rust::LANGUAGE.into()),
10249        )
10250        .with_override_query("(string_literal) @string")
10251        .unwrap(),
10252    );
10253
10254    cx.language_registry().add(rust_language.clone());
10255    cx.update_buffer(|buffer, cx| {
10256        buffer.set_language(Some(rust_language), cx);
10257    });
10258
10259    cx.set_state(
10260        &r#"
10261            let x = ˇ
10262        "#
10263        .unindent(),
10264    );
10265
10266    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10267    cx.update_editor(|editor, window, cx| {
10268        editor.handle_input("\"", window, cx);
10269    });
10270    cx.assert_editor_state(
10271        &r#"
10272            let x = "ˇ"
10273        "#
10274        .unindent(),
10275    );
10276
10277    // Inserting another quotation mark. The cursor moves across the existing
10278    // automatically-inserted quotation mark.
10279    cx.update_editor(|editor, window, cx| {
10280        editor.handle_input("\"", window, cx);
10281    });
10282    cx.assert_editor_state(
10283        &r#"
10284            let x = ""ˇ
10285        "#
10286        .unindent(),
10287    );
10288
10289    // Reset
10290    cx.set_state(
10291        &r#"
10292            let x = ˇ
10293        "#
10294        .unindent(),
10295    );
10296
10297    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
10298    cx.update_editor(|editor, window, cx| {
10299        editor.handle_input("\"", window, cx);
10300        editor.handle_input(" ", window, cx);
10301        editor.move_left(&Default::default(), window, cx);
10302        editor.handle_input("\\", window, cx);
10303        editor.handle_input("\"", window, cx);
10304    });
10305    cx.assert_editor_state(
10306        &r#"
10307            let x = "\"ˇ "
10308        "#
10309        .unindent(),
10310    );
10311
10312    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10313    // mark. Nothing is inserted.
10314    cx.update_editor(|editor, window, cx| {
10315        editor.move_right(&Default::default(), window, cx);
10316        editor.handle_input("\"", window, cx);
10317    });
10318    cx.assert_editor_state(
10319        &r#"
10320            let x = "\" "ˇ
10321        "#
10322        .unindent(),
10323    );
10324}
10325
10326#[gpui::test]
10327async fn test_surround_with_pair(cx: &mut TestAppContext) {
10328    init_test(cx, |_| {});
10329
10330    let language = Arc::new(Language::new(
10331        LanguageConfig {
10332            brackets: BracketPairConfig {
10333                pairs: vec![
10334                    BracketPair {
10335                        start: "{".to_string(),
10336                        end: "}".to_string(),
10337                        close: true,
10338                        surround: true,
10339                        newline: true,
10340                    },
10341                    BracketPair {
10342                        start: "/* ".to_string(),
10343                        end: "*/".to_string(),
10344                        close: true,
10345                        surround: true,
10346                        ..Default::default()
10347                    },
10348                ],
10349                ..Default::default()
10350            },
10351            ..Default::default()
10352        },
10353        Some(tree_sitter_rust::LANGUAGE.into()),
10354    ));
10355
10356    let text = r#"
10357        a
10358        b
10359        c
10360    "#
10361    .unindent();
10362
10363    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10364    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10365    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10366    editor
10367        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10368        .await;
10369
10370    editor.update_in(cx, |editor, window, cx| {
10371        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10372            s.select_display_ranges([
10373                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10374                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10375                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
10376            ])
10377        });
10378
10379        editor.handle_input("{", window, cx);
10380        editor.handle_input("{", window, cx);
10381        editor.handle_input("{", window, cx);
10382        assert_eq!(
10383            editor.text(cx),
10384            "
10385                {{{a}}}
10386                {{{b}}}
10387                {{{c}}}
10388            "
10389            .unindent()
10390        );
10391        assert_eq!(
10392            editor.selections.display_ranges(cx),
10393            [
10394                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
10395                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
10396                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
10397            ]
10398        );
10399
10400        editor.undo(&Undo, window, cx);
10401        editor.undo(&Undo, window, cx);
10402        editor.undo(&Undo, window, cx);
10403        assert_eq!(
10404            editor.text(cx),
10405            "
10406                a
10407                b
10408                c
10409            "
10410            .unindent()
10411        );
10412        assert_eq!(
10413            editor.selections.display_ranges(cx),
10414            [
10415                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10416                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10417                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10418            ]
10419        );
10420
10421        // Ensure inserting the first character of a multi-byte bracket pair
10422        // doesn't surround the selections with the bracket.
10423        editor.handle_input("/", window, cx);
10424        assert_eq!(
10425            editor.text(cx),
10426            "
10427                /
10428                /
10429                /
10430            "
10431            .unindent()
10432        );
10433        assert_eq!(
10434            editor.selections.display_ranges(cx),
10435            [
10436                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10437                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10438                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10439            ]
10440        );
10441
10442        editor.undo(&Undo, window, cx);
10443        assert_eq!(
10444            editor.text(cx),
10445            "
10446                a
10447                b
10448                c
10449            "
10450            .unindent()
10451        );
10452        assert_eq!(
10453            editor.selections.display_ranges(cx),
10454            [
10455                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10456                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10457                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10458            ]
10459        );
10460
10461        // Ensure inserting the last character of a multi-byte bracket pair
10462        // doesn't surround the selections with the bracket.
10463        editor.handle_input("*", window, cx);
10464        assert_eq!(
10465            editor.text(cx),
10466            "
10467                *
10468                *
10469                *
10470            "
10471            .unindent()
10472        );
10473        assert_eq!(
10474            editor.selections.display_ranges(cx),
10475            [
10476                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10477                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10478                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10479            ]
10480        );
10481    });
10482}
10483
10484#[gpui::test]
10485async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
10486    init_test(cx, |_| {});
10487
10488    let language = Arc::new(Language::new(
10489        LanguageConfig {
10490            brackets: BracketPairConfig {
10491                pairs: vec![BracketPair {
10492                    start: "{".to_string(),
10493                    end: "}".to_string(),
10494                    close: true,
10495                    surround: true,
10496                    newline: true,
10497                }],
10498                ..Default::default()
10499            },
10500            autoclose_before: "}".to_string(),
10501            ..Default::default()
10502        },
10503        Some(tree_sitter_rust::LANGUAGE.into()),
10504    ));
10505
10506    let text = r#"
10507        a
10508        b
10509        c
10510    "#
10511    .unindent();
10512
10513    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10514    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10515    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10516    editor
10517        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10518        .await;
10519
10520    editor.update_in(cx, |editor, window, cx| {
10521        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10522            s.select_ranges([
10523                Point::new(0, 1)..Point::new(0, 1),
10524                Point::new(1, 1)..Point::new(1, 1),
10525                Point::new(2, 1)..Point::new(2, 1),
10526            ])
10527        });
10528
10529        editor.handle_input("{", window, cx);
10530        editor.handle_input("{", window, cx);
10531        editor.handle_input("_", window, cx);
10532        assert_eq!(
10533            editor.text(cx),
10534            "
10535                a{{_}}
10536                b{{_}}
10537                c{{_}}
10538            "
10539            .unindent()
10540        );
10541        assert_eq!(
10542            editor.selections.ranges::<Point>(cx),
10543            [
10544                Point::new(0, 4)..Point::new(0, 4),
10545                Point::new(1, 4)..Point::new(1, 4),
10546                Point::new(2, 4)..Point::new(2, 4)
10547            ]
10548        );
10549
10550        editor.backspace(&Default::default(), window, cx);
10551        editor.backspace(&Default::default(), window, cx);
10552        assert_eq!(
10553            editor.text(cx),
10554            "
10555                a{}
10556                b{}
10557                c{}
10558            "
10559            .unindent()
10560        );
10561        assert_eq!(
10562            editor.selections.ranges::<Point>(cx),
10563            [
10564                Point::new(0, 2)..Point::new(0, 2),
10565                Point::new(1, 2)..Point::new(1, 2),
10566                Point::new(2, 2)..Point::new(2, 2)
10567            ]
10568        );
10569
10570        editor.delete_to_previous_word_start(&Default::default(), window, cx);
10571        assert_eq!(
10572            editor.text(cx),
10573            "
10574                a
10575                b
10576                c
10577            "
10578            .unindent()
10579        );
10580        assert_eq!(
10581            editor.selections.ranges::<Point>(cx),
10582            [
10583                Point::new(0, 1)..Point::new(0, 1),
10584                Point::new(1, 1)..Point::new(1, 1),
10585                Point::new(2, 1)..Point::new(2, 1)
10586            ]
10587        );
10588    });
10589}
10590
10591#[gpui::test]
10592async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
10593    init_test(cx, |settings| {
10594        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10595    });
10596
10597    let mut cx = EditorTestContext::new(cx).await;
10598
10599    let language = Arc::new(Language::new(
10600        LanguageConfig {
10601            brackets: BracketPairConfig {
10602                pairs: vec![
10603                    BracketPair {
10604                        start: "{".to_string(),
10605                        end: "}".to_string(),
10606                        close: true,
10607                        surround: true,
10608                        newline: true,
10609                    },
10610                    BracketPair {
10611                        start: "(".to_string(),
10612                        end: ")".to_string(),
10613                        close: true,
10614                        surround: true,
10615                        newline: true,
10616                    },
10617                    BracketPair {
10618                        start: "[".to_string(),
10619                        end: "]".to_string(),
10620                        close: false,
10621                        surround: true,
10622                        newline: true,
10623                    },
10624                ],
10625                ..Default::default()
10626            },
10627            autoclose_before: "})]".to_string(),
10628            ..Default::default()
10629        },
10630        Some(tree_sitter_rust::LANGUAGE.into()),
10631    ));
10632
10633    cx.language_registry().add(language.clone());
10634    cx.update_buffer(|buffer, cx| {
10635        buffer.set_language(Some(language), cx);
10636    });
10637
10638    cx.set_state(
10639        &"
10640            {(ˇ)}
10641            [[ˇ]]
10642            {(ˇ)}
10643        "
10644        .unindent(),
10645    );
10646
10647    cx.update_editor(|editor, window, cx| {
10648        editor.backspace(&Default::default(), window, cx);
10649        editor.backspace(&Default::default(), window, cx);
10650    });
10651
10652    cx.assert_editor_state(
10653        &"
10654            ˇ
10655            ˇ]]
10656            ˇ
10657        "
10658        .unindent(),
10659    );
10660
10661    cx.update_editor(|editor, window, cx| {
10662        editor.handle_input("{", window, cx);
10663        editor.handle_input("{", window, cx);
10664        editor.move_right(&MoveRight, window, cx);
10665        editor.move_right(&MoveRight, window, cx);
10666        editor.move_left(&MoveLeft, window, cx);
10667        editor.move_left(&MoveLeft, window, cx);
10668        editor.backspace(&Default::default(), window, cx);
10669    });
10670
10671    cx.assert_editor_state(
10672        &"
10673            {ˇ}
10674            {ˇ}]]
10675            {ˇ}
10676        "
10677        .unindent(),
10678    );
10679
10680    cx.update_editor(|editor, window, cx| {
10681        editor.backspace(&Default::default(), window, cx);
10682    });
10683
10684    cx.assert_editor_state(
10685        &"
10686            ˇ
10687            ˇ]]
10688            ˇ
10689        "
10690        .unindent(),
10691    );
10692}
10693
10694#[gpui::test]
10695async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
10696    init_test(cx, |_| {});
10697
10698    let language = Arc::new(Language::new(
10699        LanguageConfig::default(),
10700        Some(tree_sitter_rust::LANGUAGE.into()),
10701    ));
10702
10703    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
10704    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10705    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10706    editor
10707        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10708        .await;
10709
10710    editor.update_in(cx, |editor, window, cx| {
10711        editor.set_auto_replace_emoji_shortcode(true);
10712
10713        editor.handle_input("Hello ", window, cx);
10714        editor.handle_input(":wave", window, cx);
10715        assert_eq!(editor.text(cx), "Hello :wave".unindent());
10716
10717        editor.handle_input(":", window, cx);
10718        assert_eq!(editor.text(cx), "Hello 👋".unindent());
10719
10720        editor.handle_input(" :smile", window, cx);
10721        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
10722
10723        editor.handle_input(":", window, cx);
10724        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
10725
10726        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
10727        editor.handle_input(":wave", window, cx);
10728        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
10729
10730        editor.handle_input(":", window, cx);
10731        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
10732
10733        editor.handle_input(":1", window, cx);
10734        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
10735
10736        editor.handle_input(":", window, cx);
10737        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
10738
10739        // Ensure shortcode does not get replaced when it is part of a word
10740        editor.handle_input(" Test:wave", window, cx);
10741        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
10742
10743        editor.handle_input(":", window, cx);
10744        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
10745
10746        editor.set_auto_replace_emoji_shortcode(false);
10747
10748        // Ensure shortcode does not get replaced when auto replace is off
10749        editor.handle_input(" :wave", window, cx);
10750        assert_eq!(
10751            editor.text(cx),
10752            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
10753        );
10754
10755        editor.handle_input(":", window, cx);
10756        assert_eq!(
10757            editor.text(cx),
10758            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
10759        );
10760    });
10761}
10762
10763#[gpui::test]
10764async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
10765    init_test(cx, |_| {});
10766
10767    let (text, insertion_ranges) = marked_text_ranges(
10768        indoc! {"
10769            ˇ
10770        "},
10771        false,
10772    );
10773
10774    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
10775    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10776
10777    _ = editor.update_in(cx, |editor, window, cx| {
10778        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
10779
10780        editor
10781            .insert_snippet(&insertion_ranges, snippet, window, cx)
10782            .unwrap();
10783
10784        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
10785            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
10786            assert_eq!(editor.text(cx), expected_text);
10787            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
10788        }
10789
10790        assert(
10791            editor,
10792            cx,
10793            indoc! {"
10794            type «» =•
10795            "},
10796        );
10797
10798        assert!(editor.context_menu_visible(), "There should be a matches");
10799    });
10800}
10801
10802#[gpui::test]
10803async fn test_snippets(cx: &mut TestAppContext) {
10804    init_test(cx, |_| {});
10805
10806    let mut cx = EditorTestContext::new(cx).await;
10807
10808    cx.set_state(indoc! {"
10809        a.ˇ b
10810        a.ˇ b
10811        a.ˇ b
10812    "});
10813
10814    cx.update_editor(|editor, window, cx| {
10815        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
10816        let insertion_ranges = editor
10817            .selections
10818            .all(cx)
10819            .iter()
10820            .map(|s| s.range())
10821            .collect::<Vec<_>>();
10822        editor
10823            .insert_snippet(&insertion_ranges, snippet, window, cx)
10824            .unwrap();
10825    });
10826
10827    cx.assert_editor_state(indoc! {"
10828        a.f(«oneˇ», two, «threeˇ») b
10829        a.f(«oneˇ», two, «threeˇ») b
10830        a.f(«oneˇ», two, «threeˇ») b
10831    "});
10832
10833    // Can't move earlier than the first tab stop
10834    cx.update_editor(|editor, window, cx| {
10835        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10836    });
10837    cx.assert_editor_state(indoc! {"
10838        a.f(«oneˇ», two, «threeˇ») b
10839        a.f(«oneˇ», two, «threeˇ») b
10840        a.f(«oneˇ», two, «threeˇ») b
10841    "});
10842
10843    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10844    cx.assert_editor_state(indoc! {"
10845        a.f(one, «twoˇ», three) b
10846        a.f(one, «twoˇ», three) b
10847        a.f(one, «twoˇ», three) b
10848    "});
10849
10850    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
10851    cx.assert_editor_state(indoc! {"
10852        a.f(«oneˇ», two, «threeˇ») b
10853        a.f(«oneˇ», two, «threeˇ») b
10854        a.f(«oneˇ», two, «threeˇ») b
10855    "});
10856
10857    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10858    cx.assert_editor_state(indoc! {"
10859        a.f(one, «twoˇ», three) b
10860        a.f(one, «twoˇ», three) b
10861        a.f(one, «twoˇ», three) b
10862    "});
10863    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10864    cx.assert_editor_state(indoc! {"
10865        a.f(one, two, three)ˇ b
10866        a.f(one, two, three)ˇ b
10867        a.f(one, two, three)ˇ b
10868    "});
10869
10870    // As soon as the last tab stop is reached, snippet state is gone
10871    cx.update_editor(|editor, window, cx| {
10872        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10873    });
10874    cx.assert_editor_state(indoc! {"
10875        a.f(one, two, three)ˇ b
10876        a.f(one, two, three)ˇ b
10877        a.f(one, two, three)ˇ b
10878    "});
10879}
10880
10881#[gpui::test]
10882async fn test_snippet_indentation(cx: &mut TestAppContext) {
10883    init_test(cx, |_| {});
10884
10885    let mut cx = EditorTestContext::new(cx).await;
10886
10887    cx.update_editor(|editor, window, cx| {
10888        let snippet = Snippet::parse(indoc! {"
10889            /*
10890             * Multiline comment with leading indentation
10891             *
10892             * $1
10893             */
10894            $0"})
10895        .unwrap();
10896        let insertion_ranges = editor
10897            .selections
10898            .all(cx)
10899            .iter()
10900            .map(|s| s.range())
10901            .collect::<Vec<_>>();
10902        editor
10903            .insert_snippet(&insertion_ranges, snippet, window, cx)
10904            .unwrap();
10905    });
10906
10907    cx.assert_editor_state(indoc! {"
10908        /*
10909         * Multiline comment with leading indentation
10910         *
10911         * ˇ
10912         */
10913    "});
10914
10915    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10916    cx.assert_editor_state(indoc! {"
10917        /*
10918         * Multiline comment with leading indentation
10919         *
10920         *•
10921         */
10922        ˇ"});
10923}
10924
10925#[gpui::test]
10926async fn test_document_format_during_save(cx: &mut TestAppContext) {
10927    init_test(cx, |_| {});
10928
10929    let fs = FakeFs::new(cx.executor());
10930    fs.insert_file(path!("/file.rs"), Default::default()).await;
10931
10932    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
10933
10934    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10935    language_registry.add(rust_lang());
10936    let mut fake_servers = language_registry.register_fake_lsp(
10937        "Rust",
10938        FakeLspAdapter {
10939            capabilities: lsp::ServerCapabilities {
10940                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10941                ..Default::default()
10942            },
10943            ..Default::default()
10944        },
10945    );
10946
10947    let buffer = project
10948        .update(cx, |project, cx| {
10949            project.open_local_buffer(path!("/file.rs"), cx)
10950        })
10951        .await
10952        .unwrap();
10953
10954    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10955    let (editor, cx) = cx.add_window_view(|window, cx| {
10956        build_editor_with_project(project.clone(), buffer, window, cx)
10957    });
10958    editor.update_in(cx, |editor, window, cx| {
10959        editor.set_text("one\ntwo\nthree\n", window, cx)
10960    });
10961    assert!(cx.read(|cx| editor.is_dirty(cx)));
10962
10963    cx.executor().start_waiting();
10964    let fake_server = fake_servers.next().await.unwrap();
10965
10966    {
10967        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10968            move |params, _| async move {
10969                assert_eq!(
10970                    params.text_document.uri,
10971                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10972                );
10973                assert_eq!(params.options.tab_size, 4);
10974                Ok(Some(vec![lsp::TextEdit::new(
10975                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10976                    ", ".to_string(),
10977                )]))
10978            },
10979        );
10980        let save = editor
10981            .update_in(cx, |editor, window, cx| {
10982                editor.save(
10983                    SaveOptions {
10984                        format: true,
10985                        autosave: false,
10986                    },
10987                    project.clone(),
10988                    window,
10989                    cx,
10990                )
10991            })
10992            .unwrap();
10993        cx.executor().start_waiting();
10994        save.await;
10995
10996        assert_eq!(
10997            editor.update(cx, |editor, cx| editor.text(cx)),
10998            "one, two\nthree\n"
10999        );
11000        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11001    }
11002
11003    {
11004        editor.update_in(cx, |editor, window, cx| {
11005            editor.set_text("one\ntwo\nthree\n", window, cx)
11006        });
11007        assert!(cx.read(|cx| editor.is_dirty(cx)));
11008
11009        // Ensure we can still save even if formatting hangs.
11010        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11011            move |params, _| async move {
11012                assert_eq!(
11013                    params.text_document.uri,
11014                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11015                );
11016                futures::future::pending::<()>().await;
11017                unreachable!()
11018            },
11019        );
11020        let save = editor
11021            .update_in(cx, |editor, window, cx| {
11022                editor.save(
11023                    SaveOptions {
11024                        format: true,
11025                        autosave: false,
11026                    },
11027                    project.clone(),
11028                    window,
11029                    cx,
11030                )
11031            })
11032            .unwrap();
11033        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11034        cx.executor().start_waiting();
11035        save.await;
11036        assert_eq!(
11037            editor.update(cx, |editor, cx| editor.text(cx)),
11038            "one\ntwo\nthree\n"
11039        );
11040    }
11041
11042    // Set rust language override and assert overridden tabsize is sent to language server
11043    update_test_language_settings(cx, |settings| {
11044        settings.languages.0.insert(
11045            "Rust".into(),
11046            LanguageSettingsContent {
11047                tab_size: NonZeroU32::new(8),
11048                ..Default::default()
11049            },
11050        );
11051    });
11052
11053    {
11054        editor.update_in(cx, |editor, window, cx| {
11055            editor.set_text("somehting_new\n", window, cx)
11056        });
11057        assert!(cx.read(|cx| editor.is_dirty(cx)));
11058        let _formatting_request_signal = fake_server
11059            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11060                assert_eq!(
11061                    params.text_document.uri,
11062                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11063                );
11064                assert_eq!(params.options.tab_size, 8);
11065                Ok(Some(vec![]))
11066            });
11067        let save = editor
11068            .update_in(cx, |editor, window, cx| {
11069                editor.save(
11070                    SaveOptions {
11071                        format: true,
11072                        autosave: false,
11073                    },
11074                    project.clone(),
11075                    window,
11076                    cx,
11077                )
11078            })
11079            .unwrap();
11080        cx.executor().start_waiting();
11081        save.await;
11082    }
11083}
11084
11085#[gpui::test]
11086async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11087    init_test(cx, |settings| {
11088        settings.defaults.ensure_final_newline_on_save = Some(false);
11089    });
11090
11091    let fs = FakeFs::new(cx.executor());
11092    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11093
11094    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11095
11096    let buffer = project
11097        .update(cx, |project, cx| {
11098            project.open_local_buffer(path!("/file.txt"), cx)
11099        })
11100        .await
11101        .unwrap();
11102
11103    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11104    let (editor, cx) = cx.add_window_view(|window, cx| {
11105        build_editor_with_project(project.clone(), buffer, window, cx)
11106    });
11107    editor.update_in(cx, |editor, window, cx| {
11108        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11109            s.select_ranges([0..0])
11110        });
11111    });
11112    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11113
11114    editor.update_in(cx, |editor, window, cx| {
11115        editor.handle_input("\n", window, cx)
11116    });
11117    cx.run_until_parked();
11118    save(&editor, &project, cx).await;
11119    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11120
11121    editor.update_in(cx, |editor, window, cx| {
11122        editor.undo(&Default::default(), window, cx);
11123    });
11124    save(&editor, &project, cx).await;
11125    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11126
11127    editor.update_in(cx, |editor, window, cx| {
11128        editor.redo(&Default::default(), window, cx);
11129    });
11130    cx.run_until_parked();
11131    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11132
11133    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11134        let save = editor
11135            .update_in(cx, |editor, window, cx| {
11136                editor.save(
11137                    SaveOptions {
11138                        format: true,
11139                        autosave: false,
11140                    },
11141                    project.clone(),
11142                    window,
11143                    cx,
11144                )
11145            })
11146            .unwrap();
11147        cx.executor().start_waiting();
11148        save.await;
11149        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11150    }
11151}
11152
11153#[gpui::test]
11154async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
11155    init_test(cx, |_| {});
11156
11157    let cols = 4;
11158    let rows = 10;
11159    let sample_text_1 = sample_text(rows, cols, 'a');
11160    assert_eq!(
11161        sample_text_1,
11162        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11163    );
11164    let sample_text_2 = sample_text(rows, cols, 'l');
11165    assert_eq!(
11166        sample_text_2,
11167        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11168    );
11169    let sample_text_3 = sample_text(rows, cols, 'v');
11170    assert_eq!(
11171        sample_text_3,
11172        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11173    );
11174
11175    let fs = FakeFs::new(cx.executor());
11176    fs.insert_tree(
11177        path!("/a"),
11178        json!({
11179            "main.rs": sample_text_1,
11180            "other.rs": sample_text_2,
11181            "lib.rs": sample_text_3,
11182        }),
11183    )
11184    .await;
11185
11186    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11187    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11188    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11189
11190    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11191    language_registry.add(rust_lang());
11192    let mut fake_servers = language_registry.register_fake_lsp(
11193        "Rust",
11194        FakeLspAdapter {
11195            capabilities: lsp::ServerCapabilities {
11196                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11197                ..Default::default()
11198            },
11199            ..Default::default()
11200        },
11201    );
11202
11203    let worktree = project.update(cx, |project, cx| {
11204        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
11205        assert_eq!(worktrees.len(), 1);
11206        worktrees.pop().unwrap()
11207    });
11208    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11209
11210    let buffer_1 = project
11211        .update(cx, |project, cx| {
11212            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
11213        })
11214        .await
11215        .unwrap();
11216    let buffer_2 = project
11217        .update(cx, |project, cx| {
11218            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
11219        })
11220        .await
11221        .unwrap();
11222    let buffer_3 = project
11223        .update(cx, |project, cx| {
11224            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
11225        })
11226        .await
11227        .unwrap();
11228
11229    let multi_buffer = cx.new(|cx| {
11230        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11231        multi_buffer.push_excerpts(
11232            buffer_1.clone(),
11233            [
11234                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11235                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11236                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11237            ],
11238            cx,
11239        );
11240        multi_buffer.push_excerpts(
11241            buffer_2.clone(),
11242            [
11243                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11244                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11245                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11246            ],
11247            cx,
11248        );
11249        multi_buffer.push_excerpts(
11250            buffer_3.clone(),
11251            [
11252                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11253                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11254                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11255            ],
11256            cx,
11257        );
11258        multi_buffer
11259    });
11260    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
11261        Editor::new(
11262            EditorMode::full(),
11263            multi_buffer,
11264            Some(project.clone()),
11265            window,
11266            cx,
11267        )
11268    });
11269
11270    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11271        editor.change_selections(
11272            SelectionEffects::scroll(Autoscroll::Next),
11273            window,
11274            cx,
11275            |s| s.select_ranges(Some(1..2)),
11276        );
11277        editor.insert("|one|two|three|", window, cx);
11278    });
11279    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11280    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11281        editor.change_selections(
11282            SelectionEffects::scroll(Autoscroll::Next),
11283            window,
11284            cx,
11285            |s| s.select_ranges(Some(60..70)),
11286        );
11287        editor.insert("|four|five|six|", window, cx);
11288    });
11289    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11290
11291    // First two buffers should be edited, but not the third one.
11292    assert_eq!(
11293        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11294        "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}",
11295    );
11296    buffer_1.update(cx, |buffer, _| {
11297        assert!(buffer.is_dirty());
11298        assert_eq!(
11299            buffer.text(),
11300            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
11301        )
11302    });
11303    buffer_2.update(cx, |buffer, _| {
11304        assert!(buffer.is_dirty());
11305        assert_eq!(
11306            buffer.text(),
11307            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
11308        )
11309    });
11310    buffer_3.update(cx, |buffer, _| {
11311        assert!(!buffer.is_dirty());
11312        assert_eq!(buffer.text(), sample_text_3,)
11313    });
11314    cx.executor().run_until_parked();
11315
11316    cx.executor().start_waiting();
11317    let save = multi_buffer_editor
11318        .update_in(cx, |editor, window, cx| {
11319            editor.save(
11320                SaveOptions {
11321                    format: true,
11322                    autosave: false,
11323                },
11324                project.clone(),
11325                window,
11326                cx,
11327            )
11328        })
11329        .unwrap();
11330
11331    let fake_server = fake_servers.next().await.unwrap();
11332    fake_server
11333        .server
11334        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11335            Ok(Some(vec![lsp::TextEdit::new(
11336                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11337                format!("[{} formatted]", params.text_document.uri),
11338            )]))
11339        })
11340        .detach();
11341    save.await;
11342
11343    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11344    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11345    assert_eq!(
11346        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11347        uri!(
11348            "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}"
11349        ),
11350    );
11351    buffer_1.update(cx, |buffer, _| {
11352        assert!(!buffer.is_dirty());
11353        assert_eq!(
11354            buffer.text(),
11355            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11356        )
11357    });
11358    buffer_2.update(cx, |buffer, _| {
11359        assert!(!buffer.is_dirty());
11360        assert_eq!(
11361            buffer.text(),
11362            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11363        )
11364    });
11365    buffer_3.update(cx, |buffer, _| {
11366        assert!(!buffer.is_dirty());
11367        assert_eq!(buffer.text(), sample_text_3,)
11368    });
11369}
11370
11371#[gpui::test]
11372async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
11373    init_test(cx, |_| {});
11374
11375    let fs = FakeFs::new(cx.executor());
11376    fs.insert_tree(
11377        path!("/dir"),
11378        json!({
11379            "file1.rs": "fn main() { println!(\"hello\"); }",
11380            "file2.rs": "fn test() { println!(\"test\"); }",
11381            "file3.rs": "fn other() { println!(\"other\"); }\n",
11382        }),
11383    )
11384    .await;
11385
11386    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
11387    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11388    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11389
11390    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11391    language_registry.add(rust_lang());
11392
11393    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
11394    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11395
11396    // Open three buffers
11397    let buffer_1 = project
11398        .update(cx, |project, cx| {
11399            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
11400        })
11401        .await
11402        .unwrap();
11403    let buffer_2 = project
11404        .update(cx, |project, cx| {
11405            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
11406        })
11407        .await
11408        .unwrap();
11409    let buffer_3 = project
11410        .update(cx, |project, cx| {
11411            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
11412        })
11413        .await
11414        .unwrap();
11415
11416    // Create a multi-buffer with all three buffers
11417    let multi_buffer = cx.new(|cx| {
11418        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11419        multi_buffer.push_excerpts(
11420            buffer_1.clone(),
11421            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11422            cx,
11423        );
11424        multi_buffer.push_excerpts(
11425            buffer_2.clone(),
11426            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11427            cx,
11428        );
11429        multi_buffer.push_excerpts(
11430            buffer_3.clone(),
11431            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11432            cx,
11433        );
11434        multi_buffer
11435    });
11436
11437    let editor = cx.new_window_entity(|window, cx| {
11438        Editor::new(
11439            EditorMode::full(),
11440            multi_buffer,
11441            Some(project.clone()),
11442            window,
11443            cx,
11444        )
11445    });
11446
11447    // Edit only the first buffer
11448    editor.update_in(cx, |editor, window, cx| {
11449        editor.change_selections(
11450            SelectionEffects::scroll(Autoscroll::Next),
11451            window,
11452            cx,
11453            |s| s.select_ranges(Some(10..10)),
11454        );
11455        editor.insert("// edited", window, cx);
11456    });
11457
11458    // Verify that only buffer 1 is dirty
11459    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
11460    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11461    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11462
11463    // Get write counts after file creation (files were created with initial content)
11464    // We expect each file to have been written once during creation
11465    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
11466    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
11467    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
11468
11469    // Perform autosave
11470    let save_task = editor.update_in(cx, |editor, window, cx| {
11471        editor.save(
11472            SaveOptions {
11473                format: true,
11474                autosave: true,
11475            },
11476            project.clone(),
11477            window,
11478            cx,
11479        )
11480    });
11481    save_task.await.unwrap();
11482
11483    // Only the dirty buffer should have been saved
11484    assert_eq!(
11485        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11486        1,
11487        "Buffer 1 was dirty, so it should have been written once during autosave"
11488    );
11489    assert_eq!(
11490        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11491        0,
11492        "Buffer 2 was clean, so it should not have been written during autosave"
11493    );
11494    assert_eq!(
11495        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11496        0,
11497        "Buffer 3 was clean, so it should not have been written during autosave"
11498    );
11499
11500    // Verify buffer states after autosave
11501    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11502    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11503    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11504
11505    // Now perform a manual save (format = true)
11506    let save_task = editor.update_in(cx, |editor, window, cx| {
11507        editor.save(
11508            SaveOptions {
11509                format: true,
11510                autosave: false,
11511            },
11512            project.clone(),
11513            window,
11514            cx,
11515        )
11516    });
11517    save_task.await.unwrap();
11518
11519    // During manual save, clean buffers don't get written to disk
11520    // They just get did_save called for language server notifications
11521    assert_eq!(
11522        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11523        1,
11524        "Buffer 1 should only have been written once total (during autosave, not manual save)"
11525    );
11526    assert_eq!(
11527        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11528        0,
11529        "Buffer 2 should not have been written at all"
11530    );
11531    assert_eq!(
11532        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11533        0,
11534        "Buffer 3 should not have been written at all"
11535    );
11536}
11537
11538async fn setup_range_format_test(
11539    cx: &mut TestAppContext,
11540) -> (
11541    Entity<Project>,
11542    Entity<Editor>,
11543    &mut gpui::VisualTestContext,
11544    lsp::FakeLanguageServer,
11545) {
11546    init_test(cx, |_| {});
11547
11548    let fs = FakeFs::new(cx.executor());
11549    fs.insert_file(path!("/file.rs"), Default::default()).await;
11550
11551    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11552
11553    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11554    language_registry.add(rust_lang());
11555    let mut fake_servers = language_registry.register_fake_lsp(
11556        "Rust",
11557        FakeLspAdapter {
11558            capabilities: lsp::ServerCapabilities {
11559                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
11560                ..lsp::ServerCapabilities::default()
11561            },
11562            ..FakeLspAdapter::default()
11563        },
11564    );
11565
11566    let buffer = project
11567        .update(cx, |project, cx| {
11568            project.open_local_buffer(path!("/file.rs"), cx)
11569        })
11570        .await
11571        .unwrap();
11572
11573    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11574    let (editor, cx) = cx.add_window_view(|window, cx| {
11575        build_editor_with_project(project.clone(), buffer, window, cx)
11576    });
11577
11578    cx.executor().start_waiting();
11579    let fake_server = fake_servers.next().await.unwrap();
11580
11581    (project, editor, cx, fake_server)
11582}
11583
11584#[gpui::test]
11585async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
11586    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11587
11588    editor.update_in(cx, |editor, window, cx| {
11589        editor.set_text("one\ntwo\nthree\n", window, cx)
11590    });
11591    assert!(cx.read(|cx| editor.is_dirty(cx)));
11592
11593    let save = editor
11594        .update_in(cx, |editor, window, cx| {
11595            editor.save(
11596                SaveOptions {
11597                    format: true,
11598                    autosave: false,
11599                },
11600                project.clone(),
11601                window,
11602                cx,
11603            )
11604        })
11605        .unwrap();
11606    fake_server
11607        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11608            assert_eq!(
11609                params.text_document.uri,
11610                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11611            );
11612            assert_eq!(params.options.tab_size, 4);
11613            Ok(Some(vec![lsp::TextEdit::new(
11614                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11615                ", ".to_string(),
11616            )]))
11617        })
11618        .next()
11619        .await;
11620    cx.executor().start_waiting();
11621    save.await;
11622    assert_eq!(
11623        editor.update(cx, |editor, cx| editor.text(cx)),
11624        "one, two\nthree\n"
11625    );
11626    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11627}
11628
11629#[gpui::test]
11630async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
11631    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11632
11633    editor.update_in(cx, |editor, window, cx| {
11634        editor.set_text("one\ntwo\nthree\n", window, cx)
11635    });
11636    assert!(cx.read(|cx| editor.is_dirty(cx)));
11637
11638    // Test that save still works when formatting hangs
11639    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
11640        move |params, _| async move {
11641            assert_eq!(
11642                params.text_document.uri,
11643                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11644            );
11645            futures::future::pending::<()>().await;
11646            unreachable!()
11647        },
11648    );
11649    let save = editor
11650        .update_in(cx, |editor, window, cx| {
11651            editor.save(
11652                SaveOptions {
11653                    format: true,
11654                    autosave: false,
11655                },
11656                project.clone(),
11657                window,
11658                cx,
11659            )
11660        })
11661        .unwrap();
11662    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11663    cx.executor().start_waiting();
11664    save.await;
11665    assert_eq!(
11666        editor.update(cx, |editor, cx| editor.text(cx)),
11667        "one\ntwo\nthree\n"
11668    );
11669    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11670}
11671
11672#[gpui::test]
11673async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
11674    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11675
11676    // Buffer starts clean, no formatting should be requested
11677    let save = editor
11678        .update_in(cx, |editor, window, cx| {
11679            editor.save(
11680                SaveOptions {
11681                    format: false,
11682                    autosave: false,
11683                },
11684                project.clone(),
11685                window,
11686                cx,
11687            )
11688        })
11689        .unwrap();
11690    let _pending_format_request = fake_server
11691        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
11692            panic!("Should not be invoked");
11693        })
11694        .next();
11695    cx.executor().start_waiting();
11696    save.await;
11697    cx.run_until_parked();
11698}
11699
11700#[gpui::test]
11701async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
11702    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11703
11704    // Set Rust language override and assert overridden tabsize is sent to language server
11705    update_test_language_settings(cx, |settings| {
11706        settings.languages.0.insert(
11707            "Rust".into(),
11708            LanguageSettingsContent {
11709                tab_size: NonZeroU32::new(8),
11710                ..Default::default()
11711            },
11712        );
11713    });
11714
11715    editor.update_in(cx, |editor, window, cx| {
11716        editor.set_text("something_new\n", window, cx)
11717    });
11718    assert!(cx.read(|cx| editor.is_dirty(cx)));
11719    let save = editor
11720        .update_in(cx, |editor, window, cx| {
11721            editor.save(
11722                SaveOptions {
11723                    format: true,
11724                    autosave: false,
11725                },
11726                project.clone(),
11727                window,
11728                cx,
11729            )
11730        })
11731        .unwrap();
11732    fake_server
11733        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11734            assert_eq!(
11735                params.text_document.uri,
11736                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11737            );
11738            assert_eq!(params.options.tab_size, 8);
11739            Ok(Some(Vec::new()))
11740        })
11741        .next()
11742        .await;
11743    save.await;
11744}
11745
11746#[gpui::test]
11747async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
11748    init_test(cx, |settings| {
11749        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
11750            Formatter::LanguageServer { name: None },
11751        )))
11752    });
11753
11754    let fs = FakeFs::new(cx.executor());
11755    fs.insert_file(path!("/file.rs"), Default::default()).await;
11756
11757    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11758
11759    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11760    language_registry.add(Arc::new(Language::new(
11761        LanguageConfig {
11762            name: "Rust".into(),
11763            matcher: LanguageMatcher {
11764                path_suffixes: vec!["rs".to_string()],
11765                ..Default::default()
11766            },
11767            ..LanguageConfig::default()
11768        },
11769        Some(tree_sitter_rust::LANGUAGE.into()),
11770    )));
11771    update_test_language_settings(cx, |settings| {
11772        // Enable Prettier formatting for the same buffer, and ensure
11773        // LSP is called instead of Prettier.
11774        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
11775    });
11776    let mut fake_servers = language_registry.register_fake_lsp(
11777        "Rust",
11778        FakeLspAdapter {
11779            capabilities: lsp::ServerCapabilities {
11780                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11781                ..Default::default()
11782            },
11783            ..Default::default()
11784        },
11785    );
11786
11787    let buffer = project
11788        .update(cx, |project, cx| {
11789            project.open_local_buffer(path!("/file.rs"), cx)
11790        })
11791        .await
11792        .unwrap();
11793
11794    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11795    let (editor, cx) = cx.add_window_view(|window, cx| {
11796        build_editor_with_project(project.clone(), buffer, window, cx)
11797    });
11798    editor.update_in(cx, |editor, window, cx| {
11799        editor.set_text("one\ntwo\nthree\n", window, cx)
11800    });
11801
11802    cx.executor().start_waiting();
11803    let fake_server = fake_servers.next().await.unwrap();
11804
11805    let format = editor
11806        .update_in(cx, |editor, window, cx| {
11807            editor.perform_format(
11808                project.clone(),
11809                FormatTrigger::Manual,
11810                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11811                window,
11812                cx,
11813            )
11814        })
11815        .unwrap();
11816    fake_server
11817        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11818            assert_eq!(
11819                params.text_document.uri,
11820                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11821            );
11822            assert_eq!(params.options.tab_size, 4);
11823            Ok(Some(vec![lsp::TextEdit::new(
11824                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11825                ", ".to_string(),
11826            )]))
11827        })
11828        .next()
11829        .await;
11830    cx.executor().start_waiting();
11831    format.await;
11832    assert_eq!(
11833        editor.update(cx, |editor, cx| editor.text(cx)),
11834        "one, two\nthree\n"
11835    );
11836
11837    editor.update_in(cx, |editor, window, cx| {
11838        editor.set_text("one\ntwo\nthree\n", window, cx)
11839    });
11840    // Ensure we don't lock if formatting hangs.
11841    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11842        move |params, _| async move {
11843            assert_eq!(
11844                params.text_document.uri,
11845                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11846            );
11847            futures::future::pending::<()>().await;
11848            unreachable!()
11849        },
11850    );
11851    let format = editor
11852        .update_in(cx, |editor, window, cx| {
11853            editor.perform_format(
11854                project,
11855                FormatTrigger::Manual,
11856                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11857                window,
11858                cx,
11859            )
11860        })
11861        .unwrap();
11862    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11863    cx.executor().start_waiting();
11864    format.await;
11865    assert_eq!(
11866        editor.update(cx, |editor, cx| editor.text(cx)),
11867        "one\ntwo\nthree\n"
11868    );
11869}
11870
11871#[gpui::test]
11872async fn test_multiple_formatters(cx: &mut TestAppContext) {
11873    init_test(cx, |settings| {
11874        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
11875        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
11876            Formatter::LanguageServer { name: None },
11877            Formatter::CodeActions(
11878                [
11879                    ("code-action-1".into(), true),
11880                    ("code-action-2".into(), true),
11881                ]
11882                .into_iter()
11883                .collect(),
11884            ),
11885        ])))
11886    });
11887
11888    let fs = FakeFs::new(cx.executor());
11889    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
11890        .await;
11891
11892    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11893    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11894    language_registry.add(rust_lang());
11895
11896    let mut fake_servers = language_registry.register_fake_lsp(
11897        "Rust",
11898        FakeLspAdapter {
11899            capabilities: lsp::ServerCapabilities {
11900                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11901                execute_command_provider: Some(lsp::ExecuteCommandOptions {
11902                    commands: vec!["the-command-for-code-action-1".into()],
11903                    ..Default::default()
11904                }),
11905                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
11906                ..Default::default()
11907            },
11908            ..Default::default()
11909        },
11910    );
11911
11912    let buffer = project
11913        .update(cx, |project, cx| {
11914            project.open_local_buffer(path!("/file.rs"), cx)
11915        })
11916        .await
11917        .unwrap();
11918
11919    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11920    let (editor, cx) = cx.add_window_view(|window, cx| {
11921        build_editor_with_project(project.clone(), buffer, window, cx)
11922    });
11923
11924    cx.executor().start_waiting();
11925
11926    let fake_server = fake_servers.next().await.unwrap();
11927    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11928        move |_params, _| async move {
11929            Ok(Some(vec![lsp::TextEdit::new(
11930                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11931                "applied-formatting\n".to_string(),
11932            )]))
11933        },
11934    );
11935    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
11936        move |params, _| async move {
11937            let requested_code_actions = params.context.only.expect("Expected code action request");
11938            assert_eq!(requested_code_actions.len(), 1);
11939
11940            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
11941            let code_action = match requested_code_actions[0].as_str() {
11942                "code-action-1" => lsp::CodeAction {
11943                    kind: Some("code-action-1".into()),
11944                    edit: Some(lsp::WorkspaceEdit::new(
11945                        [(
11946                            uri,
11947                            vec![lsp::TextEdit::new(
11948                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11949                                "applied-code-action-1-edit\n".to_string(),
11950                            )],
11951                        )]
11952                        .into_iter()
11953                        .collect(),
11954                    )),
11955                    command: Some(lsp::Command {
11956                        command: "the-command-for-code-action-1".into(),
11957                        ..Default::default()
11958                    }),
11959                    ..Default::default()
11960                },
11961                "code-action-2" => lsp::CodeAction {
11962                    kind: Some("code-action-2".into()),
11963                    edit: Some(lsp::WorkspaceEdit::new(
11964                        [(
11965                            uri,
11966                            vec![lsp::TextEdit::new(
11967                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11968                                "applied-code-action-2-edit\n".to_string(),
11969                            )],
11970                        )]
11971                        .into_iter()
11972                        .collect(),
11973                    )),
11974                    ..Default::default()
11975                },
11976                req => panic!("Unexpected code action request: {:?}", req),
11977            };
11978            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
11979                code_action,
11980            )]))
11981        },
11982    );
11983
11984    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
11985        move |params, _| async move { Ok(params) }
11986    });
11987
11988    let command_lock = Arc::new(futures::lock::Mutex::new(()));
11989    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
11990        let fake = fake_server.clone();
11991        let lock = command_lock.clone();
11992        move |params, _| {
11993            assert_eq!(params.command, "the-command-for-code-action-1");
11994            let fake = fake.clone();
11995            let lock = lock.clone();
11996            async move {
11997                lock.lock().await;
11998                fake.server
11999                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
12000                        label: None,
12001                        edit: lsp::WorkspaceEdit {
12002                            changes: Some(
12003                                [(
12004                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
12005                                    vec![lsp::TextEdit {
12006                                        range: lsp::Range::new(
12007                                            lsp::Position::new(0, 0),
12008                                            lsp::Position::new(0, 0),
12009                                        ),
12010                                        new_text: "applied-code-action-1-command\n".into(),
12011                                    }],
12012                                )]
12013                                .into_iter()
12014                                .collect(),
12015                            ),
12016                            ..Default::default()
12017                        },
12018                    })
12019                    .await
12020                    .into_response()
12021                    .unwrap();
12022                Ok(Some(json!(null)))
12023            }
12024        }
12025    });
12026
12027    cx.executor().start_waiting();
12028    editor
12029        .update_in(cx, |editor, window, cx| {
12030            editor.perform_format(
12031                project.clone(),
12032                FormatTrigger::Manual,
12033                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12034                window,
12035                cx,
12036            )
12037        })
12038        .unwrap()
12039        .await;
12040    editor.update(cx, |editor, cx| {
12041        assert_eq!(
12042            editor.text(cx),
12043            r#"
12044                applied-code-action-2-edit
12045                applied-code-action-1-command
12046                applied-code-action-1-edit
12047                applied-formatting
12048                one
12049                two
12050                three
12051            "#
12052            .unindent()
12053        );
12054    });
12055
12056    editor.update_in(cx, |editor, window, cx| {
12057        editor.undo(&Default::default(), window, cx);
12058        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12059    });
12060
12061    // Perform a manual edit while waiting for an LSP command
12062    // that's being run as part of a formatting code action.
12063    let lock_guard = command_lock.lock().await;
12064    let format = editor
12065        .update_in(cx, |editor, window, cx| {
12066            editor.perform_format(
12067                project.clone(),
12068                FormatTrigger::Manual,
12069                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12070                window,
12071                cx,
12072            )
12073        })
12074        .unwrap();
12075    cx.run_until_parked();
12076    editor.update(cx, |editor, cx| {
12077        assert_eq!(
12078            editor.text(cx),
12079            r#"
12080                applied-code-action-1-edit
12081                applied-formatting
12082                one
12083                two
12084                three
12085            "#
12086            .unindent()
12087        );
12088
12089        editor.buffer.update(cx, |buffer, cx| {
12090            let ix = buffer.len(cx);
12091            buffer.edit([(ix..ix, "edited\n")], None, cx);
12092        });
12093    });
12094
12095    // Allow the LSP command to proceed. Because the buffer was edited,
12096    // the second code action will not be run.
12097    drop(lock_guard);
12098    format.await;
12099    editor.update_in(cx, |editor, window, cx| {
12100        assert_eq!(
12101            editor.text(cx),
12102            r#"
12103                applied-code-action-1-command
12104                applied-code-action-1-edit
12105                applied-formatting
12106                one
12107                two
12108                three
12109                edited
12110            "#
12111            .unindent()
12112        );
12113
12114        // The manual edit is undone first, because it is the last thing the user did
12115        // (even though the command completed afterwards).
12116        editor.undo(&Default::default(), window, cx);
12117        assert_eq!(
12118            editor.text(cx),
12119            r#"
12120                applied-code-action-1-command
12121                applied-code-action-1-edit
12122                applied-formatting
12123                one
12124                two
12125                three
12126            "#
12127            .unindent()
12128        );
12129
12130        // All the formatting (including the command, which completed after the manual edit)
12131        // is undone together.
12132        editor.undo(&Default::default(), window, cx);
12133        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12134    });
12135}
12136
12137#[gpui::test]
12138async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12139    init_test(cx, |settings| {
12140        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
12141            Formatter::LanguageServer { name: None },
12142        ])))
12143    });
12144
12145    let fs = FakeFs::new(cx.executor());
12146    fs.insert_file(path!("/file.ts"), Default::default()).await;
12147
12148    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12149
12150    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12151    language_registry.add(Arc::new(Language::new(
12152        LanguageConfig {
12153            name: "TypeScript".into(),
12154            matcher: LanguageMatcher {
12155                path_suffixes: vec!["ts".to_string()],
12156                ..Default::default()
12157            },
12158            ..LanguageConfig::default()
12159        },
12160        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12161    )));
12162    update_test_language_settings(cx, |settings| {
12163        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12164    });
12165    let mut fake_servers = language_registry.register_fake_lsp(
12166        "TypeScript",
12167        FakeLspAdapter {
12168            capabilities: lsp::ServerCapabilities {
12169                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12170                ..Default::default()
12171            },
12172            ..Default::default()
12173        },
12174    );
12175
12176    let buffer = project
12177        .update(cx, |project, cx| {
12178            project.open_local_buffer(path!("/file.ts"), cx)
12179        })
12180        .await
12181        .unwrap();
12182
12183    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12184    let (editor, cx) = cx.add_window_view(|window, cx| {
12185        build_editor_with_project(project.clone(), buffer, window, cx)
12186    });
12187    editor.update_in(cx, |editor, window, cx| {
12188        editor.set_text(
12189            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12190            window,
12191            cx,
12192        )
12193    });
12194
12195    cx.executor().start_waiting();
12196    let fake_server = fake_servers.next().await.unwrap();
12197
12198    let format = editor
12199        .update_in(cx, |editor, window, cx| {
12200            editor.perform_code_action_kind(
12201                project.clone(),
12202                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12203                window,
12204                cx,
12205            )
12206        })
12207        .unwrap();
12208    fake_server
12209        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12210            assert_eq!(
12211                params.text_document.uri,
12212                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12213            );
12214            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12215                lsp::CodeAction {
12216                    title: "Organize Imports".to_string(),
12217                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12218                    edit: Some(lsp::WorkspaceEdit {
12219                        changes: Some(
12220                            [(
12221                                params.text_document.uri.clone(),
12222                                vec![lsp::TextEdit::new(
12223                                    lsp::Range::new(
12224                                        lsp::Position::new(1, 0),
12225                                        lsp::Position::new(2, 0),
12226                                    ),
12227                                    "".to_string(),
12228                                )],
12229                            )]
12230                            .into_iter()
12231                            .collect(),
12232                        ),
12233                        ..Default::default()
12234                    }),
12235                    ..Default::default()
12236                },
12237            )]))
12238        })
12239        .next()
12240        .await;
12241    cx.executor().start_waiting();
12242    format.await;
12243    assert_eq!(
12244        editor.update(cx, |editor, cx| editor.text(cx)),
12245        "import { a } from 'module';\n\nconst x = a;\n"
12246    );
12247
12248    editor.update_in(cx, |editor, window, cx| {
12249        editor.set_text(
12250            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12251            window,
12252            cx,
12253        )
12254    });
12255    // Ensure we don't lock if code action hangs.
12256    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12257        move |params, _| async move {
12258            assert_eq!(
12259                params.text_document.uri,
12260                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12261            );
12262            futures::future::pending::<()>().await;
12263            unreachable!()
12264        },
12265    );
12266    let format = editor
12267        .update_in(cx, |editor, window, cx| {
12268            editor.perform_code_action_kind(
12269                project,
12270                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12271                window,
12272                cx,
12273            )
12274        })
12275        .unwrap();
12276    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
12277    cx.executor().start_waiting();
12278    format.await;
12279    assert_eq!(
12280        editor.update(cx, |editor, cx| editor.text(cx)),
12281        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
12282    );
12283}
12284
12285#[gpui::test]
12286async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
12287    init_test(cx, |_| {});
12288
12289    let mut cx = EditorLspTestContext::new_rust(
12290        lsp::ServerCapabilities {
12291            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12292            ..Default::default()
12293        },
12294        cx,
12295    )
12296    .await;
12297
12298    cx.set_state(indoc! {"
12299        one.twoˇ
12300    "});
12301
12302    // The format request takes a long time. When it completes, it inserts
12303    // a newline and an indent before the `.`
12304    cx.lsp
12305        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12306            let executor = cx.background_executor().clone();
12307            async move {
12308                executor.timer(Duration::from_millis(100)).await;
12309                Ok(Some(vec![lsp::TextEdit {
12310                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12311                    new_text: "\n    ".into(),
12312                }]))
12313            }
12314        });
12315
12316    // Submit a format request.
12317    let format_1 = cx
12318        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12319        .unwrap();
12320    cx.executor().run_until_parked();
12321
12322    // Submit a second format request.
12323    let format_2 = cx
12324        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12325        .unwrap();
12326    cx.executor().run_until_parked();
12327
12328    // Wait for both format requests to complete
12329    cx.executor().advance_clock(Duration::from_millis(200));
12330    cx.executor().start_waiting();
12331    format_1.await.unwrap();
12332    cx.executor().start_waiting();
12333    format_2.await.unwrap();
12334
12335    // The formatting edits only happens once.
12336    cx.assert_editor_state(indoc! {"
12337        one
12338            .twoˇ
12339    "});
12340}
12341
12342#[gpui::test]
12343async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12344    init_test(cx, |settings| {
12345        settings.defaults.formatter = Some(SelectedFormatter::Auto)
12346    });
12347
12348    let mut cx = EditorLspTestContext::new_rust(
12349        lsp::ServerCapabilities {
12350            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12351            ..Default::default()
12352        },
12353        cx,
12354    )
12355    .await;
12356
12357    // Set up a buffer white some trailing whitespace and no trailing newline.
12358    cx.set_state(
12359        &[
12360            "one ",   //
12361            "twoˇ",   //
12362            "three ", //
12363            "four",   //
12364        ]
12365        .join("\n"),
12366    );
12367
12368    // Submit a format request.
12369    let format = cx
12370        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12371        .unwrap();
12372
12373    // Record which buffer changes have been sent to the language server
12374    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12375    cx.lsp
12376        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12377            let buffer_changes = buffer_changes.clone();
12378            move |params, _| {
12379                buffer_changes.lock().extend(
12380                    params
12381                        .content_changes
12382                        .into_iter()
12383                        .map(|e| (e.range.unwrap(), e.text)),
12384                );
12385            }
12386        });
12387
12388    // Handle formatting requests to the language server.
12389    cx.lsp
12390        .set_request_handler::<lsp::request::Formatting, _, _>({
12391            let buffer_changes = buffer_changes.clone();
12392            move |_, _| {
12393                // When formatting is requested, trailing whitespace has already been stripped,
12394                // and the trailing newline has already been added.
12395                assert_eq!(
12396                    &buffer_changes.lock()[1..],
12397                    &[
12398                        (
12399                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12400                            "".into()
12401                        ),
12402                        (
12403                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12404                            "".into()
12405                        ),
12406                        (
12407                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12408                            "\n".into()
12409                        ),
12410                    ]
12411                );
12412
12413                // Insert blank lines between each line of the buffer.
12414                async move {
12415                    Ok(Some(vec![
12416                        lsp::TextEdit {
12417                            range: lsp::Range::new(
12418                                lsp::Position::new(1, 0),
12419                                lsp::Position::new(1, 0),
12420                            ),
12421                            new_text: "\n".into(),
12422                        },
12423                        lsp::TextEdit {
12424                            range: lsp::Range::new(
12425                                lsp::Position::new(2, 0),
12426                                lsp::Position::new(2, 0),
12427                            ),
12428                            new_text: "\n".into(),
12429                        },
12430                    ]))
12431                }
12432            }
12433        });
12434
12435    // After formatting the buffer, the trailing whitespace is stripped,
12436    // a newline is appended, and the edits provided by the language server
12437    // have been applied.
12438    format.await.unwrap();
12439    cx.assert_editor_state(
12440        &[
12441            "one",   //
12442            "",      //
12443            "twoˇ",  //
12444            "",      //
12445            "three", //
12446            "four",  //
12447            "",      //
12448        ]
12449        .join("\n"),
12450    );
12451
12452    // Undoing the formatting undoes the trailing whitespace removal, the
12453    // trailing newline, and the LSP edits.
12454    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12455    cx.assert_editor_state(
12456        &[
12457            "one ",   //
12458            "twoˇ",   //
12459            "three ", //
12460            "four",   //
12461        ]
12462        .join("\n"),
12463    );
12464}
12465
12466#[gpui::test]
12467async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12468    cx: &mut TestAppContext,
12469) {
12470    init_test(cx, |_| {});
12471
12472    cx.update(|cx| {
12473        cx.update_global::<SettingsStore, _>(|settings, cx| {
12474            settings.update_user_settings(cx, |settings| {
12475                settings.editor.auto_signature_help = Some(true);
12476            });
12477        });
12478    });
12479
12480    let mut cx = EditorLspTestContext::new_rust(
12481        lsp::ServerCapabilities {
12482            signature_help_provider: Some(lsp::SignatureHelpOptions {
12483                ..Default::default()
12484            }),
12485            ..Default::default()
12486        },
12487        cx,
12488    )
12489    .await;
12490
12491    let language = Language::new(
12492        LanguageConfig {
12493            name: "Rust".into(),
12494            brackets: BracketPairConfig {
12495                pairs: vec![
12496                    BracketPair {
12497                        start: "{".to_string(),
12498                        end: "}".to_string(),
12499                        close: true,
12500                        surround: true,
12501                        newline: true,
12502                    },
12503                    BracketPair {
12504                        start: "(".to_string(),
12505                        end: ")".to_string(),
12506                        close: true,
12507                        surround: true,
12508                        newline: true,
12509                    },
12510                    BracketPair {
12511                        start: "/*".to_string(),
12512                        end: " */".to_string(),
12513                        close: true,
12514                        surround: true,
12515                        newline: true,
12516                    },
12517                    BracketPair {
12518                        start: "[".to_string(),
12519                        end: "]".to_string(),
12520                        close: false,
12521                        surround: false,
12522                        newline: true,
12523                    },
12524                    BracketPair {
12525                        start: "\"".to_string(),
12526                        end: "\"".to_string(),
12527                        close: true,
12528                        surround: true,
12529                        newline: false,
12530                    },
12531                    BracketPair {
12532                        start: "<".to_string(),
12533                        end: ">".to_string(),
12534                        close: false,
12535                        surround: true,
12536                        newline: true,
12537                    },
12538                ],
12539                ..Default::default()
12540            },
12541            autoclose_before: "})]".to_string(),
12542            ..Default::default()
12543        },
12544        Some(tree_sitter_rust::LANGUAGE.into()),
12545    );
12546    let language = Arc::new(language);
12547
12548    cx.language_registry().add(language.clone());
12549    cx.update_buffer(|buffer, cx| {
12550        buffer.set_language(Some(language), cx);
12551    });
12552
12553    cx.set_state(
12554        &r#"
12555            fn main() {
12556                sampleˇ
12557            }
12558        "#
12559        .unindent(),
12560    );
12561
12562    cx.update_editor(|editor, window, cx| {
12563        editor.handle_input("(", window, cx);
12564    });
12565    cx.assert_editor_state(
12566        &"
12567            fn main() {
12568                sample(ˇ)
12569            }
12570        "
12571        .unindent(),
12572    );
12573
12574    let mocked_response = lsp::SignatureHelp {
12575        signatures: vec![lsp::SignatureInformation {
12576            label: "fn sample(param1: u8, param2: u8)".to_string(),
12577            documentation: None,
12578            parameters: Some(vec![
12579                lsp::ParameterInformation {
12580                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12581                    documentation: None,
12582                },
12583                lsp::ParameterInformation {
12584                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12585                    documentation: None,
12586                },
12587            ]),
12588            active_parameter: None,
12589        }],
12590        active_signature: Some(0),
12591        active_parameter: Some(0),
12592    };
12593    handle_signature_help_request(&mut cx, mocked_response).await;
12594
12595    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12596        .await;
12597
12598    cx.editor(|editor, _, _| {
12599        let signature_help_state = editor.signature_help_state.popover().cloned();
12600        let signature = signature_help_state.unwrap();
12601        assert_eq!(
12602            signature.signatures[signature.current_signature].label,
12603            "fn sample(param1: u8, param2: u8)"
12604        );
12605    });
12606}
12607
12608#[gpui::test]
12609async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
12610    init_test(cx, |_| {});
12611
12612    cx.update(|cx| {
12613        cx.update_global::<SettingsStore, _>(|settings, cx| {
12614            settings.update_user_settings(cx, |settings| {
12615                settings.editor.auto_signature_help = Some(false);
12616                settings.editor.show_signature_help_after_edits = Some(false);
12617            });
12618        });
12619    });
12620
12621    let mut cx = EditorLspTestContext::new_rust(
12622        lsp::ServerCapabilities {
12623            signature_help_provider: Some(lsp::SignatureHelpOptions {
12624                ..Default::default()
12625            }),
12626            ..Default::default()
12627        },
12628        cx,
12629    )
12630    .await;
12631
12632    let language = Language::new(
12633        LanguageConfig {
12634            name: "Rust".into(),
12635            brackets: BracketPairConfig {
12636                pairs: vec![
12637                    BracketPair {
12638                        start: "{".to_string(),
12639                        end: "}".to_string(),
12640                        close: true,
12641                        surround: true,
12642                        newline: true,
12643                    },
12644                    BracketPair {
12645                        start: "(".to_string(),
12646                        end: ")".to_string(),
12647                        close: true,
12648                        surround: true,
12649                        newline: true,
12650                    },
12651                    BracketPair {
12652                        start: "/*".to_string(),
12653                        end: " */".to_string(),
12654                        close: true,
12655                        surround: true,
12656                        newline: true,
12657                    },
12658                    BracketPair {
12659                        start: "[".to_string(),
12660                        end: "]".to_string(),
12661                        close: false,
12662                        surround: false,
12663                        newline: true,
12664                    },
12665                    BracketPair {
12666                        start: "\"".to_string(),
12667                        end: "\"".to_string(),
12668                        close: true,
12669                        surround: true,
12670                        newline: false,
12671                    },
12672                    BracketPair {
12673                        start: "<".to_string(),
12674                        end: ">".to_string(),
12675                        close: false,
12676                        surround: true,
12677                        newline: true,
12678                    },
12679                ],
12680                ..Default::default()
12681            },
12682            autoclose_before: "})]".to_string(),
12683            ..Default::default()
12684        },
12685        Some(tree_sitter_rust::LANGUAGE.into()),
12686    );
12687    let language = Arc::new(language);
12688
12689    cx.language_registry().add(language.clone());
12690    cx.update_buffer(|buffer, cx| {
12691        buffer.set_language(Some(language), cx);
12692    });
12693
12694    // Ensure that signature_help is not called when no signature help is enabled.
12695    cx.set_state(
12696        &r#"
12697            fn main() {
12698                sampleˇ
12699            }
12700        "#
12701        .unindent(),
12702    );
12703    cx.update_editor(|editor, window, cx| {
12704        editor.handle_input("(", window, cx);
12705    });
12706    cx.assert_editor_state(
12707        &"
12708            fn main() {
12709                sample(ˇ)
12710            }
12711        "
12712        .unindent(),
12713    );
12714    cx.editor(|editor, _, _| {
12715        assert!(editor.signature_help_state.task().is_none());
12716    });
12717
12718    let mocked_response = lsp::SignatureHelp {
12719        signatures: vec![lsp::SignatureInformation {
12720            label: "fn sample(param1: u8, param2: u8)".to_string(),
12721            documentation: None,
12722            parameters: Some(vec![
12723                lsp::ParameterInformation {
12724                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12725                    documentation: None,
12726                },
12727                lsp::ParameterInformation {
12728                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12729                    documentation: None,
12730                },
12731            ]),
12732            active_parameter: None,
12733        }],
12734        active_signature: Some(0),
12735        active_parameter: Some(0),
12736    };
12737
12738    // Ensure that signature_help is called when enabled afte edits
12739    cx.update(|_, cx| {
12740        cx.update_global::<SettingsStore, _>(|settings, cx| {
12741            settings.update_user_settings(cx, |settings| {
12742                settings.editor.auto_signature_help = Some(false);
12743                settings.editor.show_signature_help_after_edits = Some(true);
12744            });
12745        });
12746    });
12747    cx.set_state(
12748        &r#"
12749            fn main() {
12750                sampleˇ
12751            }
12752        "#
12753        .unindent(),
12754    );
12755    cx.update_editor(|editor, window, cx| {
12756        editor.handle_input("(", window, cx);
12757    });
12758    cx.assert_editor_state(
12759        &"
12760            fn main() {
12761                sample(ˇ)
12762            }
12763        "
12764        .unindent(),
12765    );
12766    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12767    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12768        .await;
12769    cx.update_editor(|editor, _, _| {
12770        let signature_help_state = editor.signature_help_state.popover().cloned();
12771        assert!(signature_help_state.is_some());
12772        let signature = signature_help_state.unwrap();
12773        assert_eq!(
12774            signature.signatures[signature.current_signature].label,
12775            "fn sample(param1: u8, param2: u8)"
12776        );
12777        editor.signature_help_state = SignatureHelpState::default();
12778    });
12779
12780    // Ensure that signature_help is called when auto signature help override is enabled
12781    cx.update(|_, cx| {
12782        cx.update_global::<SettingsStore, _>(|settings, cx| {
12783            settings.update_user_settings(cx, |settings| {
12784                settings.editor.auto_signature_help = Some(true);
12785                settings.editor.show_signature_help_after_edits = Some(false);
12786            });
12787        });
12788    });
12789    cx.set_state(
12790        &r#"
12791            fn main() {
12792                sampleˇ
12793            }
12794        "#
12795        .unindent(),
12796    );
12797    cx.update_editor(|editor, window, cx| {
12798        editor.handle_input("(", window, cx);
12799    });
12800    cx.assert_editor_state(
12801        &"
12802            fn main() {
12803                sample(ˇ)
12804            }
12805        "
12806        .unindent(),
12807    );
12808    handle_signature_help_request(&mut cx, mocked_response).await;
12809    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12810        .await;
12811    cx.editor(|editor, _, _| {
12812        let signature_help_state = editor.signature_help_state.popover().cloned();
12813        assert!(signature_help_state.is_some());
12814        let signature = signature_help_state.unwrap();
12815        assert_eq!(
12816            signature.signatures[signature.current_signature].label,
12817            "fn sample(param1: u8, param2: u8)"
12818        );
12819    });
12820}
12821
12822#[gpui::test]
12823async fn test_signature_help(cx: &mut TestAppContext) {
12824    init_test(cx, |_| {});
12825    cx.update(|cx| {
12826        cx.update_global::<SettingsStore, _>(|settings, cx| {
12827            settings.update_user_settings(cx, |settings| {
12828                settings.editor.auto_signature_help = Some(true);
12829            });
12830        });
12831    });
12832
12833    let mut cx = EditorLspTestContext::new_rust(
12834        lsp::ServerCapabilities {
12835            signature_help_provider: Some(lsp::SignatureHelpOptions {
12836                ..Default::default()
12837            }),
12838            ..Default::default()
12839        },
12840        cx,
12841    )
12842    .await;
12843
12844    // A test that directly calls `show_signature_help`
12845    cx.update_editor(|editor, window, cx| {
12846        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12847    });
12848
12849    let mocked_response = lsp::SignatureHelp {
12850        signatures: vec![lsp::SignatureInformation {
12851            label: "fn sample(param1: u8, param2: u8)".to_string(),
12852            documentation: None,
12853            parameters: Some(vec![
12854                lsp::ParameterInformation {
12855                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12856                    documentation: None,
12857                },
12858                lsp::ParameterInformation {
12859                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12860                    documentation: None,
12861                },
12862            ]),
12863            active_parameter: None,
12864        }],
12865        active_signature: Some(0),
12866        active_parameter: Some(0),
12867    };
12868    handle_signature_help_request(&mut cx, mocked_response).await;
12869
12870    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12871        .await;
12872
12873    cx.editor(|editor, _, _| {
12874        let signature_help_state = editor.signature_help_state.popover().cloned();
12875        assert!(signature_help_state.is_some());
12876        let signature = signature_help_state.unwrap();
12877        assert_eq!(
12878            signature.signatures[signature.current_signature].label,
12879            "fn sample(param1: u8, param2: u8)"
12880        );
12881    });
12882
12883    // When exiting outside from inside the brackets, `signature_help` is closed.
12884    cx.set_state(indoc! {"
12885        fn main() {
12886            sample(ˇ);
12887        }
12888
12889        fn sample(param1: u8, param2: u8) {}
12890    "});
12891
12892    cx.update_editor(|editor, window, cx| {
12893        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12894            s.select_ranges([0..0])
12895        });
12896    });
12897
12898    let mocked_response = lsp::SignatureHelp {
12899        signatures: Vec::new(),
12900        active_signature: None,
12901        active_parameter: None,
12902    };
12903    handle_signature_help_request(&mut cx, mocked_response).await;
12904
12905    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
12906        .await;
12907
12908    cx.editor(|editor, _, _| {
12909        assert!(!editor.signature_help_state.is_shown());
12910    });
12911
12912    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
12913    cx.set_state(indoc! {"
12914        fn main() {
12915            sample(ˇ);
12916        }
12917
12918        fn sample(param1: u8, param2: u8) {}
12919    "});
12920
12921    let mocked_response = lsp::SignatureHelp {
12922        signatures: vec![lsp::SignatureInformation {
12923            label: "fn sample(param1: u8, param2: u8)".to_string(),
12924            documentation: None,
12925            parameters: Some(vec![
12926                lsp::ParameterInformation {
12927                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12928                    documentation: None,
12929                },
12930                lsp::ParameterInformation {
12931                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12932                    documentation: None,
12933                },
12934            ]),
12935            active_parameter: None,
12936        }],
12937        active_signature: Some(0),
12938        active_parameter: Some(0),
12939    };
12940    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12941    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12942        .await;
12943    cx.editor(|editor, _, _| {
12944        assert!(editor.signature_help_state.is_shown());
12945    });
12946
12947    // Restore the popover with more parameter input
12948    cx.set_state(indoc! {"
12949        fn main() {
12950            sample(param1, param2ˇ);
12951        }
12952
12953        fn sample(param1: u8, param2: u8) {}
12954    "});
12955
12956    let mocked_response = lsp::SignatureHelp {
12957        signatures: vec![lsp::SignatureInformation {
12958            label: "fn sample(param1: u8, param2: u8)".to_string(),
12959            documentation: None,
12960            parameters: Some(vec![
12961                lsp::ParameterInformation {
12962                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12963                    documentation: None,
12964                },
12965                lsp::ParameterInformation {
12966                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12967                    documentation: None,
12968                },
12969            ]),
12970            active_parameter: None,
12971        }],
12972        active_signature: Some(0),
12973        active_parameter: Some(1),
12974    };
12975    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12976    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12977        .await;
12978
12979    // When selecting a range, the popover is gone.
12980    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
12981    cx.update_editor(|editor, window, cx| {
12982        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12983            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
12984        })
12985    });
12986    cx.assert_editor_state(indoc! {"
12987        fn main() {
12988            sample(param1, «ˇparam2»);
12989        }
12990
12991        fn sample(param1: u8, param2: u8) {}
12992    "});
12993    cx.editor(|editor, _, _| {
12994        assert!(!editor.signature_help_state.is_shown());
12995    });
12996
12997    // When unselecting again, the popover is back if within the brackets.
12998    cx.update_editor(|editor, window, cx| {
12999        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13000            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13001        })
13002    });
13003    cx.assert_editor_state(indoc! {"
13004        fn main() {
13005            sample(param1, ˇparam2);
13006        }
13007
13008        fn sample(param1: u8, param2: u8) {}
13009    "});
13010    handle_signature_help_request(&mut cx, mocked_response).await;
13011    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13012        .await;
13013    cx.editor(|editor, _, _| {
13014        assert!(editor.signature_help_state.is_shown());
13015    });
13016
13017    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13018    cx.update_editor(|editor, window, cx| {
13019        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13020            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13021            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13022        })
13023    });
13024    cx.assert_editor_state(indoc! {"
13025        fn main() {
13026            sample(param1, ˇparam2);
13027        }
13028
13029        fn sample(param1: u8, param2: u8) {}
13030    "});
13031
13032    let mocked_response = lsp::SignatureHelp {
13033        signatures: vec![lsp::SignatureInformation {
13034            label: "fn sample(param1: u8, param2: u8)".to_string(),
13035            documentation: None,
13036            parameters: Some(vec![
13037                lsp::ParameterInformation {
13038                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13039                    documentation: None,
13040                },
13041                lsp::ParameterInformation {
13042                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13043                    documentation: None,
13044                },
13045            ]),
13046            active_parameter: None,
13047        }],
13048        active_signature: Some(0),
13049        active_parameter: Some(1),
13050    };
13051    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13052    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13053        .await;
13054    cx.update_editor(|editor, _, cx| {
13055        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13056    });
13057    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13058        .await;
13059    cx.update_editor(|editor, window, cx| {
13060        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13061            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13062        })
13063    });
13064    cx.assert_editor_state(indoc! {"
13065        fn main() {
13066            sample(param1, «ˇparam2»);
13067        }
13068
13069        fn sample(param1: u8, param2: u8) {}
13070    "});
13071    cx.update_editor(|editor, window, cx| {
13072        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13073            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13074        })
13075    });
13076    cx.assert_editor_state(indoc! {"
13077        fn main() {
13078            sample(param1, ˇparam2);
13079        }
13080
13081        fn sample(param1: u8, param2: u8) {}
13082    "});
13083    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13084        .await;
13085}
13086
13087#[gpui::test]
13088async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13089    init_test(cx, |_| {});
13090
13091    let mut cx = EditorLspTestContext::new_rust(
13092        lsp::ServerCapabilities {
13093            signature_help_provider: Some(lsp::SignatureHelpOptions {
13094                ..Default::default()
13095            }),
13096            ..Default::default()
13097        },
13098        cx,
13099    )
13100    .await;
13101
13102    cx.set_state(indoc! {"
13103        fn main() {
13104            overloadedˇ
13105        }
13106    "});
13107
13108    cx.update_editor(|editor, window, cx| {
13109        editor.handle_input("(", window, cx);
13110        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13111    });
13112
13113    // Mock response with 3 signatures
13114    let mocked_response = lsp::SignatureHelp {
13115        signatures: vec![
13116            lsp::SignatureInformation {
13117                label: "fn overloaded(x: i32)".to_string(),
13118                documentation: None,
13119                parameters: Some(vec![lsp::ParameterInformation {
13120                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13121                    documentation: None,
13122                }]),
13123                active_parameter: None,
13124            },
13125            lsp::SignatureInformation {
13126                label: "fn overloaded(x: i32, y: i32)".to_string(),
13127                documentation: None,
13128                parameters: Some(vec![
13129                    lsp::ParameterInformation {
13130                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13131                        documentation: None,
13132                    },
13133                    lsp::ParameterInformation {
13134                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13135                        documentation: None,
13136                    },
13137                ]),
13138                active_parameter: None,
13139            },
13140            lsp::SignatureInformation {
13141                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13142                documentation: None,
13143                parameters: Some(vec![
13144                    lsp::ParameterInformation {
13145                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13146                        documentation: None,
13147                    },
13148                    lsp::ParameterInformation {
13149                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13150                        documentation: None,
13151                    },
13152                    lsp::ParameterInformation {
13153                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13154                        documentation: None,
13155                    },
13156                ]),
13157                active_parameter: None,
13158            },
13159        ],
13160        active_signature: Some(1),
13161        active_parameter: Some(0),
13162    };
13163    handle_signature_help_request(&mut cx, mocked_response).await;
13164
13165    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13166        .await;
13167
13168    // Verify we have multiple signatures and the right one is selected
13169    cx.editor(|editor, _, _| {
13170        let popover = editor.signature_help_state.popover().cloned().unwrap();
13171        assert_eq!(popover.signatures.len(), 3);
13172        // active_signature was 1, so that should be the current
13173        assert_eq!(popover.current_signature, 1);
13174        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13175        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13176        assert_eq!(
13177            popover.signatures[2].label,
13178            "fn overloaded(x: i32, y: i32, z: i32)"
13179        );
13180    });
13181
13182    // Test navigation functionality
13183    cx.update_editor(|editor, window, cx| {
13184        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13185    });
13186
13187    cx.editor(|editor, _, _| {
13188        let popover = editor.signature_help_state.popover().cloned().unwrap();
13189        assert_eq!(popover.current_signature, 2);
13190    });
13191
13192    // Test wrap around
13193    cx.update_editor(|editor, window, cx| {
13194        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13195    });
13196
13197    cx.editor(|editor, _, _| {
13198        let popover = editor.signature_help_state.popover().cloned().unwrap();
13199        assert_eq!(popover.current_signature, 0);
13200    });
13201
13202    // Test previous navigation
13203    cx.update_editor(|editor, window, cx| {
13204        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13205    });
13206
13207    cx.editor(|editor, _, _| {
13208        let popover = editor.signature_help_state.popover().cloned().unwrap();
13209        assert_eq!(popover.current_signature, 2);
13210    });
13211}
13212
13213#[gpui::test]
13214async fn test_completion_mode(cx: &mut TestAppContext) {
13215    init_test(cx, |_| {});
13216    let mut cx = EditorLspTestContext::new_rust(
13217        lsp::ServerCapabilities {
13218            completion_provider: Some(lsp::CompletionOptions {
13219                resolve_provider: Some(true),
13220                ..Default::default()
13221            }),
13222            ..Default::default()
13223        },
13224        cx,
13225    )
13226    .await;
13227
13228    struct Run {
13229        run_description: &'static str,
13230        initial_state: String,
13231        buffer_marked_text: String,
13232        completion_label: &'static str,
13233        completion_text: &'static str,
13234        expected_with_insert_mode: String,
13235        expected_with_replace_mode: String,
13236        expected_with_replace_subsequence_mode: String,
13237        expected_with_replace_suffix_mode: String,
13238    }
13239
13240    let runs = [
13241        Run {
13242            run_description: "Start of word matches completion text",
13243            initial_state: "before ediˇ after".into(),
13244            buffer_marked_text: "before <edi|> after".into(),
13245            completion_label: "editor",
13246            completion_text: "editor",
13247            expected_with_insert_mode: "before editorˇ after".into(),
13248            expected_with_replace_mode: "before editorˇ after".into(),
13249            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13250            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13251        },
13252        Run {
13253            run_description: "Accept same text at the middle of the word",
13254            initial_state: "before ediˇtor after".into(),
13255            buffer_marked_text: "before <edi|tor> after".into(),
13256            completion_label: "editor",
13257            completion_text: "editor",
13258            expected_with_insert_mode: "before editorˇtor after".into(),
13259            expected_with_replace_mode: "before editorˇ after".into(),
13260            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13261            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13262        },
13263        Run {
13264            run_description: "End of word matches completion text -- cursor at end",
13265            initial_state: "before torˇ after".into(),
13266            buffer_marked_text: "before <tor|> after".into(),
13267            completion_label: "editor",
13268            completion_text: "editor",
13269            expected_with_insert_mode: "before editorˇ after".into(),
13270            expected_with_replace_mode: "before editorˇ after".into(),
13271            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13272            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13273        },
13274        Run {
13275            run_description: "End of word matches completion text -- cursor at start",
13276            initial_state: "before ˇtor after".into(),
13277            buffer_marked_text: "before <|tor> after".into(),
13278            completion_label: "editor",
13279            completion_text: "editor",
13280            expected_with_insert_mode: "before editorˇtor after".into(),
13281            expected_with_replace_mode: "before editorˇ after".into(),
13282            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13283            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13284        },
13285        Run {
13286            run_description: "Prepend text containing whitespace",
13287            initial_state: "pˇfield: bool".into(),
13288            buffer_marked_text: "<p|field>: bool".into(),
13289            completion_label: "pub ",
13290            completion_text: "pub ",
13291            expected_with_insert_mode: "pub ˇfield: bool".into(),
13292            expected_with_replace_mode: "pub ˇ: bool".into(),
13293            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13294            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13295        },
13296        Run {
13297            run_description: "Add element to start of list",
13298            initial_state: "[element_ˇelement_2]".into(),
13299            buffer_marked_text: "[<element_|element_2>]".into(),
13300            completion_label: "element_1",
13301            completion_text: "element_1",
13302            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13303            expected_with_replace_mode: "[element_1ˇ]".into(),
13304            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13305            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13306        },
13307        Run {
13308            run_description: "Add element to start of list -- first and second elements are equal",
13309            initial_state: "[elˇelement]".into(),
13310            buffer_marked_text: "[<el|element>]".into(),
13311            completion_label: "element",
13312            completion_text: "element",
13313            expected_with_insert_mode: "[elementˇelement]".into(),
13314            expected_with_replace_mode: "[elementˇ]".into(),
13315            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13316            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13317        },
13318        Run {
13319            run_description: "Ends with matching suffix",
13320            initial_state: "SubˇError".into(),
13321            buffer_marked_text: "<Sub|Error>".into(),
13322            completion_label: "SubscriptionError",
13323            completion_text: "SubscriptionError",
13324            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13325            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13326            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13327            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13328        },
13329        Run {
13330            run_description: "Suffix is a subsequence -- contiguous",
13331            initial_state: "SubˇErr".into(),
13332            buffer_marked_text: "<Sub|Err>".into(),
13333            completion_label: "SubscriptionError",
13334            completion_text: "SubscriptionError",
13335            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13336            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13337            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13338            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13339        },
13340        Run {
13341            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13342            initial_state: "Suˇscrirr".into(),
13343            buffer_marked_text: "<Su|scrirr>".into(),
13344            completion_label: "SubscriptionError",
13345            completion_text: "SubscriptionError",
13346            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13347            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13348            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13349            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13350        },
13351        Run {
13352            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13353            initial_state: "foo(indˇix)".into(),
13354            buffer_marked_text: "foo(<ind|ix>)".into(),
13355            completion_label: "node_index",
13356            completion_text: "node_index",
13357            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13358            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13359            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13360            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13361        },
13362        Run {
13363            run_description: "Replace range ends before cursor - should extend to cursor",
13364            initial_state: "before editˇo after".into(),
13365            buffer_marked_text: "before <{ed}>it|o after".into(),
13366            completion_label: "editor",
13367            completion_text: "editor",
13368            expected_with_insert_mode: "before editorˇo after".into(),
13369            expected_with_replace_mode: "before editorˇo after".into(),
13370            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13371            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13372        },
13373        Run {
13374            run_description: "Uses label for suffix matching",
13375            initial_state: "before ediˇtor after".into(),
13376            buffer_marked_text: "before <edi|tor> after".into(),
13377            completion_label: "editor",
13378            completion_text: "editor()",
13379            expected_with_insert_mode: "before editor()ˇtor after".into(),
13380            expected_with_replace_mode: "before editor()ˇ after".into(),
13381            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13382            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13383        },
13384        Run {
13385            run_description: "Case insensitive subsequence and suffix matching",
13386            initial_state: "before EDiˇtoR after".into(),
13387            buffer_marked_text: "before <EDi|toR> after".into(),
13388            completion_label: "editor",
13389            completion_text: "editor",
13390            expected_with_insert_mode: "before editorˇtoR after".into(),
13391            expected_with_replace_mode: "before editorˇ after".into(),
13392            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13393            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13394        },
13395    ];
13396
13397    for run in runs {
13398        let run_variations = [
13399            (LspInsertMode::Insert, run.expected_with_insert_mode),
13400            (LspInsertMode::Replace, run.expected_with_replace_mode),
13401            (
13402                LspInsertMode::ReplaceSubsequence,
13403                run.expected_with_replace_subsequence_mode,
13404            ),
13405            (
13406                LspInsertMode::ReplaceSuffix,
13407                run.expected_with_replace_suffix_mode,
13408            ),
13409        ];
13410
13411        for (lsp_insert_mode, expected_text) in run_variations {
13412            eprintln!(
13413                "run = {:?}, mode = {lsp_insert_mode:.?}",
13414                run.run_description,
13415            );
13416
13417            update_test_language_settings(&mut cx, |settings| {
13418                settings.defaults.completions = Some(CompletionSettingsContent {
13419                    lsp_insert_mode: Some(lsp_insert_mode),
13420                    words: Some(WordsCompletionMode::Disabled),
13421                    words_min_length: Some(0),
13422                    ..Default::default()
13423                });
13424            });
13425
13426            cx.set_state(&run.initial_state);
13427            cx.update_editor(|editor, window, cx| {
13428                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13429            });
13430
13431            let counter = Arc::new(AtomicUsize::new(0));
13432            handle_completion_request_with_insert_and_replace(
13433                &mut cx,
13434                &run.buffer_marked_text,
13435                vec![(run.completion_label, run.completion_text)],
13436                counter.clone(),
13437            )
13438            .await;
13439            cx.condition(|editor, _| editor.context_menu_visible())
13440                .await;
13441            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13442
13443            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13444                editor
13445                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13446                    .unwrap()
13447            });
13448            cx.assert_editor_state(&expected_text);
13449            handle_resolve_completion_request(&mut cx, None).await;
13450            apply_additional_edits.await.unwrap();
13451        }
13452    }
13453}
13454
13455#[gpui::test]
13456async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13457    init_test(cx, |_| {});
13458    let mut cx = EditorLspTestContext::new_rust(
13459        lsp::ServerCapabilities {
13460            completion_provider: Some(lsp::CompletionOptions {
13461                resolve_provider: Some(true),
13462                ..Default::default()
13463            }),
13464            ..Default::default()
13465        },
13466        cx,
13467    )
13468    .await;
13469
13470    let initial_state = "SubˇError";
13471    let buffer_marked_text = "<Sub|Error>";
13472    let completion_text = "SubscriptionError";
13473    let expected_with_insert_mode = "SubscriptionErrorˇError";
13474    let expected_with_replace_mode = "SubscriptionErrorˇ";
13475
13476    update_test_language_settings(&mut cx, |settings| {
13477        settings.defaults.completions = Some(CompletionSettingsContent {
13478            words: Some(WordsCompletionMode::Disabled),
13479            words_min_length: Some(0),
13480            // set the opposite here to ensure that the action is overriding the default behavior
13481            lsp_insert_mode: Some(LspInsertMode::Insert),
13482            ..Default::default()
13483        });
13484    });
13485
13486    cx.set_state(initial_state);
13487    cx.update_editor(|editor, window, cx| {
13488        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13489    });
13490
13491    let counter = Arc::new(AtomicUsize::new(0));
13492    handle_completion_request_with_insert_and_replace(
13493        &mut cx,
13494        buffer_marked_text,
13495        vec![(completion_text, completion_text)],
13496        counter.clone(),
13497    )
13498    .await;
13499    cx.condition(|editor, _| editor.context_menu_visible())
13500        .await;
13501    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13502
13503    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13504        editor
13505            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13506            .unwrap()
13507    });
13508    cx.assert_editor_state(expected_with_replace_mode);
13509    handle_resolve_completion_request(&mut cx, None).await;
13510    apply_additional_edits.await.unwrap();
13511
13512    update_test_language_settings(&mut cx, |settings| {
13513        settings.defaults.completions = Some(CompletionSettingsContent {
13514            words: Some(WordsCompletionMode::Disabled),
13515            words_min_length: Some(0),
13516            // set the opposite here to ensure that the action is overriding the default behavior
13517            lsp_insert_mode: Some(LspInsertMode::Replace),
13518            ..Default::default()
13519        });
13520    });
13521
13522    cx.set_state(initial_state);
13523    cx.update_editor(|editor, window, cx| {
13524        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13525    });
13526    handle_completion_request_with_insert_and_replace(
13527        &mut cx,
13528        buffer_marked_text,
13529        vec![(completion_text, completion_text)],
13530        counter.clone(),
13531    )
13532    .await;
13533    cx.condition(|editor, _| editor.context_menu_visible())
13534        .await;
13535    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13536
13537    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13538        editor
13539            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13540            .unwrap()
13541    });
13542    cx.assert_editor_state(expected_with_insert_mode);
13543    handle_resolve_completion_request(&mut cx, None).await;
13544    apply_additional_edits.await.unwrap();
13545}
13546
13547#[gpui::test]
13548async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
13549    init_test(cx, |_| {});
13550    let mut cx = EditorLspTestContext::new_rust(
13551        lsp::ServerCapabilities {
13552            completion_provider: Some(lsp::CompletionOptions {
13553                resolve_provider: Some(true),
13554                ..Default::default()
13555            }),
13556            ..Default::default()
13557        },
13558        cx,
13559    )
13560    .await;
13561
13562    // scenario: surrounding text matches completion text
13563    let completion_text = "to_offset";
13564    let initial_state = indoc! {"
13565        1. buf.to_offˇsuffix
13566        2. buf.to_offˇsuf
13567        3. buf.to_offˇfix
13568        4. buf.to_offˇ
13569        5. into_offˇensive
13570        6. ˇsuffix
13571        7. let ˇ //
13572        8. aaˇzz
13573        9. buf.to_off«zzzzzˇ»suffix
13574        10. buf.«ˇzzzzz»suffix
13575        11. to_off«ˇzzzzz»
13576
13577        buf.to_offˇsuffix  // newest cursor
13578    "};
13579    let completion_marked_buffer = indoc! {"
13580        1. buf.to_offsuffix
13581        2. buf.to_offsuf
13582        3. buf.to_offfix
13583        4. buf.to_off
13584        5. into_offensive
13585        6. suffix
13586        7. let  //
13587        8. aazz
13588        9. buf.to_offzzzzzsuffix
13589        10. buf.zzzzzsuffix
13590        11. to_offzzzzz
13591
13592        buf.<to_off|suffix>  // newest cursor
13593    "};
13594    let expected = indoc! {"
13595        1. buf.to_offsetˇ
13596        2. buf.to_offsetˇsuf
13597        3. buf.to_offsetˇfix
13598        4. buf.to_offsetˇ
13599        5. into_offsetˇensive
13600        6. to_offsetˇsuffix
13601        7. let to_offsetˇ //
13602        8. aato_offsetˇzz
13603        9. buf.to_offsetˇ
13604        10. buf.to_offsetˇsuffix
13605        11. to_offsetˇ
13606
13607        buf.to_offsetˇ  // newest cursor
13608    "};
13609    cx.set_state(initial_state);
13610    cx.update_editor(|editor, window, cx| {
13611        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13612    });
13613    handle_completion_request_with_insert_and_replace(
13614        &mut cx,
13615        completion_marked_buffer,
13616        vec![(completion_text, completion_text)],
13617        Arc::new(AtomicUsize::new(0)),
13618    )
13619    .await;
13620    cx.condition(|editor, _| editor.context_menu_visible())
13621        .await;
13622    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13623        editor
13624            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13625            .unwrap()
13626    });
13627    cx.assert_editor_state(expected);
13628    handle_resolve_completion_request(&mut cx, None).await;
13629    apply_additional_edits.await.unwrap();
13630
13631    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
13632    let completion_text = "foo_and_bar";
13633    let initial_state = indoc! {"
13634        1. ooanbˇ
13635        2. zooanbˇ
13636        3. ooanbˇz
13637        4. zooanbˇz
13638        5. ooanˇ
13639        6. oanbˇ
13640
13641        ooanbˇ
13642    "};
13643    let completion_marked_buffer = indoc! {"
13644        1. ooanb
13645        2. zooanb
13646        3. ooanbz
13647        4. zooanbz
13648        5. ooan
13649        6. oanb
13650
13651        <ooanb|>
13652    "};
13653    let expected = indoc! {"
13654        1. foo_and_barˇ
13655        2. zfoo_and_barˇ
13656        3. foo_and_barˇz
13657        4. zfoo_and_barˇz
13658        5. ooanfoo_and_barˇ
13659        6. oanbfoo_and_barˇ
13660
13661        foo_and_barˇ
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, inserted at the middle
13686    // (expects the same as if it was inserted at the end)
13687    let completion_text = "foo_and_bar";
13688    let initial_state = indoc! {"
13689        1. ooˇanb
13690        2. zooˇanb
13691        3. ooˇanbz
13692        4. zooˇanbz
13693
13694        ooˇanb
13695    "};
13696    let completion_marked_buffer = indoc! {"
13697        1. ooanb
13698        2. zooanb
13699        3. ooanbz
13700        4. zooanbz
13701
13702        <oo|anb>
13703    "};
13704    let expected = indoc! {"
13705        1. foo_and_barˇ
13706        2. zfoo_and_barˇ
13707        3. foo_and_barˇz
13708        4. zfoo_and_barˇz
13709
13710        foo_and_barˇ
13711    "};
13712    cx.set_state(initial_state);
13713    cx.update_editor(|editor, window, cx| {
13714        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13715    });
13716    handle_completion_request_with_insert_and_replace(
13717        &mut cx,
13718        completion_marked_buffer,
13719        vec![(completion_text, completion_text)],
13720        Arc::new(AtomicUsize::new(0)),
13721    )
13722    .await;
13723    cx.condition(|editor, _| editor.context_menu_visible())
13724        .await;
13725    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13726        editor
13727            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13728            .unwrap()
13729    });
13730    cx.assert_editor_state(expected);
13731    handle_resolve_completion_request(&mut cx, None).await;
13732    apply_additional_edits.await.unwrap();
13733}
13734
13735// This used to crash
13736#[gpui::test]
13737async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
13738    init_test(cx, |_| {});
13739
13740    let buffer_text = indoc! {"
13741        fn main() {
13742            10.satu;
13743
13744            //
13745            // separate cursors so they open in different excerpts (manually reproducible)
13746            //
13747
13748            10.satu20;
13749        }
13750    "};
13751    let multibuffer_text_with_selections = indoc! {"
13752        fn main() {
13753            10.satuˇ;
13754
13755            //
13756
13757            //
13758
13759            10.satuˇ20;
13760        }
13761    "};
13762    let expected_multibuffer = indoc! {"
13763        fn main() {
13764            10.saturating_sub()ˇ;
13765
13766            //
13767
13768            //
13769
13770            10.saturating_sub()ˇ;
13771        }
13772    "};
13773
13774    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
13775    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
13776
13777    let fs = FakeFs::new(cx.executor());
13778    fs.insert_tree(
13779        path!("/a"),
13780        json!({
13781            "main.rs": buffer_text,
13782        }),
13783    )
13784    .await;
13785
13786    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13787    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13788    language_registry.add(rust_lang());
13789    let mut fake_servers = language_registry.register_fake_lsp(
13790        "Rust",
13791        FakeLspAdapter {
13792            capabilities: lsp::ServerCapabilities {
13793                completion_provider: Some(lsp::CompletionOptions {
13794                    resolve_provider: None,
13795                    ..lsp::CompletionOptions::default()
13796                }),
13797                ..lsp::ServerCapabilities::default()
13798            },
13799            ..FakeLspAdapter::default()
13800        },
13801    );
13802    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13803    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13804    let buffer = project
13805        .update(cx, |project, cx| {
13806            project.open_local_buffer(path!("/a/main.rs"), cx)
13807        })
13808        .await
13809        .unwrap();
13810
13811    let multi_buffer = cx.new(|cx| {
13812        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
13813        multi_buffer.push_excerpts(
13814            buffer.clone(),
13815            [ExcerptRange::new(0..first_excerpt_end)],
13816            cx,
13817        );
13818        multi_buffer.push_excerpts(
13819            buffer.clone(),
13820            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
13821            cx,
13822        );
13823        multi_buffer
13824    });
13825
13826    let editor = workspace
13827        .update(cx, |_, window, cx| {
13828            cx.new(|cx| {
13829                Editor::new(
13830                    EditorMode::Full {
13831                        scale_ui_elements_with_buffer_font_size: false,
13832                        show_active_line_background: false,
13833                        sized_by_content: false,
13834                    },
13835                    multi_buffer.clone(),
13836                    Some(project.clone()),
13837                    window,
13838                    cx,
13839                )
13840            })
13841        })
13842        .unwrap();
13843
13844    let pane = workspace
13845        .update(cx, |workspace, _, _| workspace.active_pane().clone())
13846        .unwrap();
13847    pane.update_in(cx, |pane, window, cx| {
13848        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
13849    });
13850
13851    let fake_server = fake_servers.next().await.unwrap();
13852
13853    editor.update_in(cx, |editor, window, cx| {
13854        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13855            s.select_ranges([
13856                Point::new(1, 11)..Point::new(1, 11),
13857                Point::new(7, 11)..Point::new(7, 11),
13858            ])
13859        });
13860
13861        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
13862    });
13863
13864    editor.update_in(cx, |editor, window, cx| {
13865        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13866    });
13867
13868    fake_server
13869        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13870            let completion_item = lsp::CompletionItem {
13871                label: "saturating_sub()".into(),
13872                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
13873                    lsp::InsertReplaceEdit {
13874                        new_text: "saturating_sub()".to_owned(),
13875                        insert: lsp::Range::new(
13876                            lsp::Position::new(7, 7),
13877                            lsp::Position::new(7, 11),
13878                        ),
13879                        replace: lsp::Range::new(
13880                            lsp::Position::new(7, 7),
13881                            lsp::Position::new(7, 13),
13882                        ),
13883                    },
13884                )),
13885                ..lsp::CompletionItem::default()
13886            };
13887
13888            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
13889        })
13890        .next()
13891        .await
13892        .unwrap();
13893
13894    cx.condition(&editor, |editor, _| editor.context_menu_visible())
13895        .await;
13896
13897    editor
13898        .update_in(cx, |editor, window, cx| {
13899            editor
13900                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13901                .unwrap()
13902        })
13903        .await
13904        .unwrap();
13905
13906    editor.update(cx, |editor, cx| {
13907        assert_text_with_selections(editor, expected_multibuffer, cx);
13908    })
13909}
13910
13911#[gpui::test]
13912async fn test_completion(cx: &mut TestAppContext) {
13913    init_test(cx, |_| {});
13914
13915    let mut cx = EditorLspTestContext::new_rust(
13916        lsp::ServerCapabilities {
13917            completion_provider: Some(lsp::CompletionOptions {
13918                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13919                resolve_provider: Some(true),
13920                ..Default::default()
13921            }),
13922            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13923            ..Default::default()
13924        },
13925        cx,
13926    )
13927    .await;
13928    let counter = Arc::new(AtomicUsize::new(0));
13929
13930    cx.set_state(indoc! {"
13931        oneˇ
13932        two
13933        three
13934    "});
13935    cx.simulate_keystroke(".");
13936    handle_completion_request(
13937        indoc! {"
13938            one.|<>
13939            two
13940            three
13941        "},
13942        vec!["first_completion", "second_completion"],
13943        true,
13944        counter.clone(),
13945        &mut cx,
13946    )
13947    .await;
13948    cx.condition(|editor, _| editor.context_menu_visible())
13949        .await;
13950    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13951
13952    let _handler = handle_signature_help_request(
13953        &mut cx,
13954        lsp::SignatureHelp {
13955            signatures: vec![lsp::SignatureInformation {
13956                label: "test signature".to_string(),
13957                documentation: None,
13958                parameters: Some(vec![lsp::ParameterInformation {
13959                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
13960                    documentation: None,
13961                }]),
13962                active_parameter: None,
13963            }],
13964            active_signature: None,
13965            active_parameter: None,
13966        },
13967    );
13968    cx.update_editor(|editor, window, cx| {
13969        assert!(
13970            !editor.signature_help_state.is_shown(),
13971            "No signature help was called for"
13972        );
13973        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13974    });
13975    cx.run_until_parked();
13976    cx.update_editor(|editor, _, _| {
13977        assert!(
13978            !editor.signature_help_state.is_shown(),
13979            "No signature help should be shown when completions menu is open"
13980        );
13981    });
13982
13983    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13984        editor.context_menu_next(&Default::default(), window, cx);
13985        editor
13986            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13987            .unwrap()
13988    });
13989    cx.assert_editor_state(indoc! {"
13990        one.second_completionˇ
13991        two
13992        three
13993    "});
13994
13995    handle_resolve_completion_request(
13996        &mut cx,
13997        Some(vec![
13998            (
13999                //This overlaps with the primary completion edit which is
14000                //misbehavior from the LSP spec, test that we filter it out
14001                indoc! {"
14002                    one.second_ˇcompletion
14003                    two
14004                    threeˇ
14005                "},
14006                "overlapping additional edit",
14007            ),
14008            (
14009                indoc! {"
14010                    one.second_completion
14011                    two
14012                    threeˇ
14013                "},
14014                "\nadditional edit",
14015            ),
14016        ]),
14017    )
14018    .await;
14019    apply_additional_edits.await.unwrap();
14020    cx.assert_editor_state(indoc! {"
14021        one.second_completionˇ
14022        two
14023        three
14024        additional edit
14025    "});
14026
14027    cx.set_state(indoc! {"
14028        one.second_completion
14029        twoˇ
14030        threeˇ
14031        additional edit
14032    "});
14033    cx.simulate_keystroke(" ");
14034    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14035    cx.simulate_keystroke("s");
14036    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14037
14038    cx.assert_editor_state(indoc! {"
14039        one.second_completion
14040        two sˇ
14041        three sˇ
14042        additional edit
14043    "});
14044    handle_completion_request(
14045        indoc! {"
14046            one.second_completion
14047            two s
14048            three <s|>
14049            additional edit
14050        "},
14051        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14052        true,
14053        counter.clone(),
14054        &mut cx,
14055    )
14056    .await;
14057    cx.condition(|editor, _| editor.context_menu_visible())
14058        .await;
14059    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14060
14061    cx.simulate_keystroke("i");
14062
14063    handle_completion_request(
14064        indoc! {"
14065            one.second_completion
14066            two si
14067            three <si|>
14068            additional edit
14069        "},
14070        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14071        true,
14072        counter.clone(),
14073        &mut cx,
14074    )
14075    .await;
14076    cx.condition(|editor, _| editor.context_menu_visible())
14077        .await;
14078    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14079
14080    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14081        editor
14082            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14083            .unwrap()
14084    });
14085    cx.assert_editor_state(indoc! {"
14086        one.second_completion
14087        two sixth_completionˇ
14088        three sixth_completionˇ
14089        additional edit
14090    "});
14091
14092    apply_additional_edits.await.unwrap();
14093
14094    update_test_language_settings(&mut cx, |settings| {
14095        settings.defaults.show_completions_on_input = Some(false);
14096    });
14097    cx.set_state("editorˇ");
14098    cx.simulate_keystroke(".");
14099    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14100    cx.simulate_keystrokes("c l o");
14101    cx.assert_editor_state("editor.cloˇ");
14102    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14103    cx.update_editor(|editor, window, cx| {
14104        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14105    });
14106    handle_completion_request(
14107        "editor.<clo|>",
14108        vec!["close", "clobber"],
14109        true,
14110        counter.clone(),
14111        &mut cx,
14112    )
14113    .await;
14114    cx.condition(|editor, _| editor.context_menu_visible())
14115        .await;
14116    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14117
14118    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14119        editor
14120            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14121            .unwrap()
14122    });
14123    cx.assert_editor_state("editor.clobberˇ");
14124    handle_resolve_completion_request(&mut cx, None).await;
14125    apply_additional_edits.await.unwrap();
14126}
14127
14128#[gpui::test]
14129async fn test_completion_reuse(cx: &mut TestAppContext) {
14130    init_test(cx, |_| {});
14131
14132    let mut cx = EditorLspTestContext::new_rust(
14133        lsp::ServerCapabilities {
14134            completion_provider: Some(lsp::CompletionOptions {
14135                trigger_characters: Some(vec![".".to_string()]),
14136                ..Default::default()
14137            }),
14138            ..Default::default()
14139        },
14140        cx,
14141    )
14142    .await;
14143
14144    let counter = Arc::new(AtomicUsize::new(0));
14145    cx.set_state("objˇ");
14146    cx.simulate_keystroke(".");
14147
14148    // Initial completion request returns complete results
14149    let is_incomplete = false;
14150    handle_completion_request(
14151        "obj.|<>",
14152        vec!["a", "ab", "abc"],
14153        is_incomplete,
14154        counter.clone(),
14155        &mut cx,
14156    )
14157    .await;
14158    cx.run_until_parked();
14159    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14160    cx.assert_editor_state("obj.ˇ");
14161    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14162
14163    // Type "a" - filters existing completions
14164    cx.simulate_keystroke("a");
14165    cx.run_until_parked();
14166    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14167    cx.assert_editor_state("obj.aˇ");
14168    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14169
14170    // Type "b" - filters existing completions
14171    cx.simulate_keystroke("b");
14172    cx.run_until_parked();
14173    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14174    cx.assert_editor_state("obj.abˇ");
14175    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14176
14177    // Type "c" - filters existing completions
14178    cx.simulate_keystroke("c");
14179    cx.run_until_parked();
14180    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14181    cx.assert_editor_state("obj.abcˇ");
14182    check_displayed_completions(vec!["abc"], &mut cx);
14183
14184    // Backspace to delete "c" - filters existing completions
14185    cx.update_editor(|editor, window, cx| {
14186        editor.backspace(&Backspace, window, cx);
14187    });
14188    cx.run_until_parked();
14189    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14190    cx.assert_editor_state("obj.abˇ");
14191    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14192
14193    // Moving cursor to the left dismisses menu.
14194    cx.update_editor(|editor, window, cx| {
14195        editor.move_left(&MoveLeft, window, cx);
14196    });
14197    cx.run_until_parked();
14198    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14199    cx.assert_editor_state("obj.aˇb");
14200    cx.update_editor(|editor, _, _| {
14201        assert_eq!(editor.context_menu_visible(), false);
14202    });
14203
14204    // Type "b" - new request
14205    cx.simulate_keystroke("b");
14206    let is_incomplete = false;
14207    handle_completion_request(
14208        "obj.<ab|>a",
14209        vec!["ab", "abc"],
14210        is_incomplete,
14211        counter.clone(),
14212        &mut cx,
14213    )
14214    .await;
14215    cx.run_until_parked();
14216    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14217    cx.assert_editor_state("obj.abˇb");
14218    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14219
14220    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14221    cx.update_editor(|editor, window, cx| {
14222        editor.backspace(&Backspace, window, cx);
14223    });
14224    let is_incomplete = false;
14225    handle_completion_request(
14226        "obj.<a|>b",
14227        vec!["a", "ab", "abc"],
14228        is_incomplete,
14229        counter.clone(),
14230        &mut cx,
14231    )
14232    .await;
14233    cx.run_until_parked();
14234    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14235    cx.assert_editor_state("obj.aˇb");
14236    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14237
14238    // Backspace to delete "a" - dismisses menu.
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), 3);
14244    cx.assert_editor_state("obj.ˇb");
14245    cx.update_editor(|editor, _, _| {
14246        assert_eq!(editor.context_menu_visible(), false);
14247    });
14248}
14249
14250#[gpui::test]
14251async fn test_word_completion(cx: &mut TestAppContext) {
14252    let lsp_fetch_timeout_ms = 10;
14253    init_test(cx, |language_settings| {
14254        language_settings.defaults.completions = Some(CompletionSettingsContent {
14255            words_min_length: Some(0),
14256            lsp_fetch_timeout_ms: Some(10),
14257            lsp_insert_mode: Some(LspInsertMode::Insert),
14258            ..Default::default()
14259        });
14260    });
14261
14262    let mut cx = EditorLspTestContext::new_rust(
14263        lsp::ServerCapabilities {
14264            completion_provider: Some(lsp::CompletionOptions {
14265                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14266                ..lsp::CompletionOptions::default()
14267            }),
14268            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14269            ..lsp::ServerCapabilities::default()
14270        },
14271        cx,
14272    )
14273    .await;
14274
14275    let throttle_completions = Arc::new(AtomicBool::new(false));
14276
14277    let lsp_throttle_completions = throttle_completions.clone();
14278    let _completion_requests_handler =
14279        cx.lsp
14280            .server
14281            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14282                let lsp_throttle_completions = lsp_throttle_completions.clone();
14283                let cx = cx.clone();
14284                async move {
14285                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14286                        cx.background_executor()
14287                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14288                            .await;
14289                    }
14290                    Ok(Some(lsp::CompletionResponse::Array(vec![
14291                        lsp::CompletionItem {
14292                            label: "first".into(),
14293                            ..lsp::CompletionItem::default()
14294                        },
14295                        lsp::CompletionItem {
14296                            label: "last".into(),
14297                            ..lsp::CompletionItem::default()
14298                        },
14299                    ])))
14300                }
14301            });
14302
14303    cx.set_state(indoc! {"
14304        oneˇ
14305        two
14306        three
14307    "});
14308    cx.simulate_keystroke(".");
14309    cx.executor().run_until_parked();
14310    cx.condition(|editor, _| editor.context_menu_visible())
14311        .await;
14312    cx.update_editor(|editor, window, cx| {
14313        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14314        {
14315            assert_eq!(
14316                completion_menu_entries(menu),
14317                &["first", "last"],
14318                "When LSP server is fast to reply, no fallback word completions are used"
14319            );
14320        } else {
14321            panic!("expected completion menu to be open");
14322        }
14323        editor.cancel(&Cancel, window, cx);
14324    });
14325    cx.executor().run_until_parked();
14326    cx.condition(|editor, _| !editor.context_menu_visible())
14327        .await;
14328
14329    throttle_completions.store(true, atomic::Ordering::Release);
14330    cx.simulate_keystroke(".");
14331    cx.executor()
14332        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14333    cx.executor().run_until_parked();
14334    cx.condition(|editor, _| editor.context_menu_visible())
14335        .await;
14336    cx.update_editor(|editor, _, _| {
14337        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14338        {
14339            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14340                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14341        } else {
14342            panic!("expected completion menu to be open");
14343        }
14344    });
14345}
14346
14347#[gpui::test]
14348async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14349    init_test(cx, |language_settings| {
14350        language_settings.defaults.completions = Some(CompletionSettingsContent {
14351            words: Some(WordsCompletionMode::Enabled),
14352            words_min_length: Some(0),
14353            lsp_insert_mode: Some(LspInsertMode::Insert),
14354            ..Default::default()
14355        });
14356    });
14357
14358    let mut cx = EditorLspTestContext::new_rust(
14359        lsp::ServerCapabilities {
14360            completion_provider: Some(lsp::CompletionOptions {
14361                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14362                ..lsp::CompletionOptions::default()
14363            }),
14364            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14365            ..lsp::ServerCapabilities::default()
14366        },
14367        cx,
14368    )
14369    .await;
14370
14371    let _completion_requests_handler =
14372        cx.lsp
14373            .server
14374            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14375                Ok(Some(lsp::CompletionResponse::Array(vec![
14376                    lsp::CompletionItem {
14377                        label: "first".into(),
14378                        ..lsp::CompletionItem::default()
14379                    },
14380                    lsp::CompletionItem {
14381                        label: "last".into(),
14382                        ..lsp::CompletionItem::default()
14383                    },
14384                ])))
14385            });
14386
14387    cx.set_state(indoc! {"ˇ
14388        first
14389        last
14390        second
14391    "});
14392    cx.simulate_keystroke(".");
14393    cx.executor().run_until_parked();
14394    cx.condition(|editor, _| editor.context_menu_visible())
14395        .await;
14396    cx.update_editor(|editor, _, _| {
14397        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14398        {
14399            assert_eq!(
14400                completion_menu_entries(menu),
14401                &["first", "last", "second"],
14402                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14403            );
14404        } else {
14405            panic!("expected completion menu to be open");
14406        }
14407    });
14408}
14409
14410#[gpui::test]
14411async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14412    init_test(cx, |language_settings| {
14413        language_settings.defaults.completions = Some(CompletionSettingsContent {
14414            words: Some(WordsCompletionMode::Disabled),
14415            words_min_length: Some(0),
14416            lsp_insert_mode: Some(LspInsertMode::Insert),
14417            ..Default::default()
14418        });
14419    });
14420
14421    let mut cx = EditorLspTestContext::new_rust(
14422        lsp::ServerCapabilities {
14423            completion_provider: Some(lsp::CompletionOptions {
14424                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14425                ..lsp::CompletionOptions::default()
14426            }),
14427            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14428            ..lsp::ServerCapabilities::default()
14429        },
14430        cx,
14431    )
14432    .await;
14433
14434    let _completion_requests_handler =
14435        cx.lsp
14436            .server
14437            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14438                panic!("LSP completions should not be queried when dealing with word completions")
14439            });
14440
14441    cx.set_state(indoc! {"ˇ
14442        first
14443        last
14444        second
14445    "});
14446    cx.update_editor(|editor, window, cx| {
14447        editor.show_word_completions(&ShowWordCompletions, window, cx);
14448    });
14449    cx.executor().run_until_parked();
14450    cx.condition(|editor, _| editor.context_menu_visible())
14451        .await;
14452    cx.update_editor(|editor, _, _| {
14453        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14454        {
14455            assert_eq!(
14456                completion_menu_entries(menu),
14457                &["first", "last", "second"],
14458                "`ShowWordCompletions` action should show word completions"
14459            );
14460        } else {
14461            panic!("expected completion menu to be open");
14462        }
14463    });
14464
14465    cx.simulate_keystroke("l");
14466    cx.executor().run_until_parked();
14467    cx.condition(|editor, _| editor.context_menu_visible())
14468        .await;
14469    cx.update_editor(|editor, _, _| {
14470        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14471        {
14472            assert_eq!(
14473                completion_menu_entries(menu),
14474                &["last"],
14475                "After showing word completions, further editing should filter them and not query the LSP"
14476            );
14477        } else {
14478            panic!("expected completion menu to be open");
14479        }
14480    });
14481}
14482
14483#[gpui::test]
14484async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14485    init_test(cx, |language_settings| {
14486        language_settings.defaults.completions = Some(CompletionSettingsContent {
14487            words_min_length: Some(0),
14488            lsp: Some(false),
14489            lsp_insert_mode: Some(LspInsertMode::Insert),
14490            ..Default::default()
14491        });
14492    });
14493
14494    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14495
14496    cx.set_state(indoc! {"ˇ
14497        0_usize
14498        let
14499        33
14500        4.5f32
14501    "});
14502    cx.update_editor(|editor, window, cx| {
14503        editor.show_completions(&ShowCompletions::default(), window, cx);
14504    });
14505    cx.executor().run_until_parked();
14506    cx.condition(|editor, _| editor.context_menu_visible())
14507        .await;
14508    cx.update_editor(|editor, window, cx| {
14509        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14510        {
14511            assert_eq!(
14512                completion_menu_entries(menu),
14513                &["let"],
14514                "With no digits in the completion query, no digits should be in the word completions"
14515            );
14516        } else {
14517            panic!("expected completion menu to be open");
14518        }
14519        editor.cancel(&Cancel, window, cx);
14520    });
14521
14522    cx.set_state(indoc! {"14523        0_usize
14524        let
14525        3
14526        33.35f32
14527    "});
14528    cx.update_editor(|editor, window, cx| {
14529        editor.show_completions(&ShowCompletions::default(), window, cx);
14530    });
14531    cx.executor().run_until_parked();
14532    cx.condition(|editor, _| editor.context_menu_visible())
14533        .await;
14534    cx.update_editor(|editor, _, _| {
14535        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14536        {
14537            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14538                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14539        } else {
14540            panic!("expected completion menu to be open");
14541        }
14542    });
14543}
14544
14545#[gpui::test]
14546async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
14547    init_test(cx, |language_settings| {
14548        language_settings.defaults.completions = Some(CompletionSettingsContent {
14549            words: Some(WordsCompletionMode::Enabled),
14550            words_min_length: Some(3),
14551            lsp_insert_mode: Some(LspInsertMode::Insert),
14552            ..Default::default()
14553        });
14554    });
14555
14556    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14557    cx.set_state(indoc! {"ˇ
14558        wow
14559        wowen
14560        wowser
14561    "});
14562    cx.simulate_keystroke("w");
14563    cx.executor().run_until_parked();
14564    cx.update_editor(|editor, _, _| {
14565        if editor.context_menu.borrow_mut().is_some() {
14566            panic!(
14567                "expected completion menu to be hidden, as words completion threshold is not met"
14568            );
14569        }
14570    });
14571
14572    cx.update_editor(|editor, window, cx| {
14573        editor.show_word_completions(&ShowWordCompletions, window, cx);
14574    });
14575    cx.executor().run_until_parked();
14576    cx.update_editor(|editor, window, cx| {
14577        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14578        {
14579            assert_eq!(completion_menu_entries(menu), &["wowser", "wowen", "wow"], "Even though the threshold is not met, invoking word completions with an action should provide the completions");
14580        } else {
14581            panic!("expected completion menu to be open after the word completions are called with an action");
14582        }
14583
14584        editor.cancel(&Cancel, window, cx);
14585    });
14586    cx.update_editor(|editor, _, _| {
14587        if editor.context_menu.borrow_mut().is_some() {
14588            panic!("expected completion menu to be hidden after canceling");
14589        }
14590    });
14591
14592    cx.simulate_keystroke("o");
14593    cx.executor().run_until_parked();
14594    cx.update_editor(|editor, _, _| {
14595        if editor.context_menu.borrow_mut().is_some() {
14596            panic!(
14597                "expected completion menu to be hidden, as words completion threshold is not met still"
14598            );
14599        }
14600    });
14601
14602    cx.simulate_keystroke("w");
14603    cx.executor().run_until_parked();
14604    cx.update_editor(|editor, _, _| {
14605        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14606        {
14607            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
14608        } else {
14609            panic!("expected completion menu to be open after the word completions threshold is met");
14610        }
14611    });
14612}
14613
14614#[gpui::test]
14615async fn test_word_completions_disabled(cx: &mut TestAppContext) {
14616    init_test(cx, |language_settings| {
14617        language_settings.defaults.completions = Some(CompletionSettingsContent {
14618            words: Some(WordsCompletionMode::Enabled),
14619            words_min_length: Some(0),
14620            lsp_insert_mode: Some(LspInsertMode::Insert),
14621            ..Default::default()
14622        });
14623    });
14624
14625    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14626    cx.update_editor(|editor, _, _| {
14627        editor.disable_word_completions();
14628    });
14629    cx.set_state(indoc! {"ˇ
14630        wow
14631        wowen
14632        wowser
14633    "});
14634    cx.simulate_keystroke("w");
14635    cx.executor().run_until_parked();
14636    cx.update_editor(|editor, _, _| {
14637        if editor.context_menu.borrow_mut().is_some() {
14638            panic!(
14639                "expected completion menu to be hidden, as words completion are disabled for this editor"
14640            );
14641        }
14642    });
14643
14644    cx.update_editor(|editor, window, cx| {
14645        editor.show_word_completions(&ShowWordCompletions, window, cx);
14646    });
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 even if called for explicitly, as words completion are disabled for this editor"
14652            );
14653        }
14654    });
14655}
14656
14657fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
14658    let position = || lsp::Position {
14659        line: params.text_document_position.position.line,
14660        character: params.text_document_position.position.character,
14661    };
14662    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14663        range: lsp::Range {
14664            start: position(),
14665            end: position(),
14666        },
14667        new_text: text.to_string(),
14668    }))
14669}
14670
14671#[gpui::test]
14672async fn test_multiline_completion(cx: &mut TestAppContext) {
14673    init_test(cx, |_| {});
14674
14675    let fs = FakeFs::new(cx.executor());
14676    fs.insert_tree(
14677        path!("/a"),
14678        json!({
14679            "main.ts": "a",
14680        }),
14681    )
14682    .await;
14683
14684    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14685    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14686    let typescript_language = Arc::new(Language::new(
14687        LanguageConfig {
14688            name: "TypeScript".into(),
14689            matcher: LanguageMatcher {
14690                path_suffixes: vec!["ts".to_string()],
14691                ..LanguageMatcher::default()
14692            },
14693            line_comments: vec!["// ".into()],
14694            ..LanguageConfig::default()
14695        },
14696        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14697    ));
14698    language_registry.add(typescript_language.clone());
14699    let mut fake_servers = language_registry.register_fake_lsp(
14700        "TypeScript",
14701        FakeLspAdapter {
14702            capabilities: lsp::ServerCapabilities {
14703                completion_provider: Some(lsp::CompletionOptions {
14704                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14705                    ..lsp::CompletionOptions::default()
14706                }),
14707                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14708                ..lsp::ServerCapabilities::default()
14709            },
14710            // Emulate vtsls label generation
14711            label_for_completion: Some(Box::new(|item, _| {
14712                let text = if let Some(description) = item
14713                    .label_details
14714                    .as_ref()
14715                    .and_then(|label_details| label_details.description.as_ref())
14716                {
14717                    format!("{} {}", item.label, description)
14718                } else if let Some(detail) = &item.detail {
14719                    format!("{} {}", item.label, detail)
14720                } else {
14721                    item.label.clone()
14722                };
14723                let len = text.len();
14724                Some(language::CodeLabel {
14725                    text,
14726                    runs: Vec::new(),
14727                    filter_range: 0..len,
14728                })
14729            })),
14730            ..FakeLspAdapter::default()
14731        },
14732    );
14733    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14734    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14735    let worktree_id = workspace
14736        .update(cx, |workspace, _window, cx| {
14737            workspace.project().update(cx, |project, cx| {
14738                project.worktrees(cx).next().unwrap().read(cx).id()
14739            })
14740        })
14741        .unwrap();
14742    let _buffer = project
14743        .update(cx, |project, cx| {
14744            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
14745        })
14746        .await
14747        .unwrap();
14748    let editor = workspace
14749        .update(cx, |workspace, window, cx| {
14750            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
14751        })
14752        .unwrap()
14753        .await
14754        .unwrap()
14755        .downcast::<Editor>()
14756        .unwrap();
14757    let fake_server = fake_servers.next().await.unwrap();
14758
14759    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
14760    let multiline_label_2 = "a\nb\nc\n";
14761    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
14762    let multiline_description = "d\ne\nf\n";
14763    let multiline_detail_2 = "g\nh\ni\n";
14764
14765    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
14766        move |params, _| async move {
14767            Ok(Some(lsp::CompletionResponse::Array(vec![
14768                lsp::CompletionItem {
14769                    label: multiline_label.to_string(),
14770                    text_edit: gen_text_edit(&params, "new_text_1"),
14771                    ..lsp::CompletionItem::default()
14772                },
14773                lsp::CompletionItem {
14774                    label: "single line label 1".to_string(),
14775                    detail: Some(multiline_detail.to_string()),
14776                    text_edit: gen_text_edit(&params, "new_text_2"),
14777                    ..lsp::CompletionItem::default()
14778                },
14779                lsp::CompletionItem {
14780                    label: "single line label 2".to_string(),
14781                    label_details: Some(lsp::CompletionItemLabelDetails {
14782                        description: Some(multiline_description.to_string()),
14783                        detail: None,
14784                    }),
14785                    text_edit: gen_text_edit(&params, "new_text_2"),
14786                    ..lsp::CompletionItem::default()
14787                },
14788                lsp::CompletionItem {
14789                    label: multiline_label_2.to_string(),
14790                    detail: Some(multiline_detail_2.to_string()),
14791                    text_edit: gen_text_edit(&params, "new_text_3"),
14792                    ..lsp::CompletionItem::default()
14793                },
14794                lsp::CompletionItem {
14795                    label: "Label with many     spaces and \t but without newlines".to_string(),
14796                    detail: Some(
14797                        "Details with many     spaces and \t but without newlines".to_string(),
14798                    ),
14799                    text_edit: gen_text_edit(&params, "new_text_4"),
14800                    ..lsp::CompletionItem::default()
14801                },
14802            ])))
14803        },
14804    );
14805
14806    editor.update_in(cx, |editor, window, cx| {
14807        cx.focus_self(window);
14808        editor.move_to_end(&MoveToEnd, window, cx);
14809        editor.handle_input(".", window, cx);
14810    });
14811    cx.run_until_parked();
14812    completion_handle.next().await.unwrap();
14813
14814    editor.update(cx, |editor, _| {
14815        assert!(editor.context_menu_visible());
14816        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14817        {
14818            let completion_labels = menu
14819                .completions
14820                .borrow()
14821                .iter()
14822                .map(|c| c.label.text.clone())
14823                .collect::<Vec<_>>();
14824            assert_eq!(
14825                completion_labels,
14826                &[
14827                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
14828                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
14829                    "single line label 2 d e f ",
14830                    "a b c g h i ",
14831                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
14832                ],
14833                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
14834            );
14835
14836            for completion in menu
14837                .completions
14838                .borrow()
14839                .iter() {
14840                    assert_eq!(
14841                        completion.label.filter_range,
14842                        0..completion.label.text.len(),
14843                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
14844                    );
14845                }
14846        } else {
14847            panic!("expected completion menu to be open");
14848        }
14849    });
14850}
14851
14852#[gpui::test]
14853async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
14854    init_test(cx, |_| {});
14855    let mut cx = EditorLspTestContext::new_rust(
14856        lsp::ServerCapabilities {
14857            completion_provider: Some(lsp::CompletionOptions {
14858                trigger_characters: Some(vec![".".to_string()]),
14859                ..Default::default()
14860            }),
14861            ..Default::default()
14862        },
14863        cx,
14864    )
14865    .await;
14866    cx.lsp
14867        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14868            Ok(Some(lsp::CompletionResponse::Array(vec![
14869                lsp::CompletionItem {
14870                    label: "first".into(),
14871                    ..Default::default()
14872                },
14873                lsp::CompletionItem {
14874                    label: "last".into(),
14875                    ..Default::default()
14876                },
14877            ])))
14878        });
14879    cx.set_state("variableˇ");
14880    cx.simulate_keystroke(".");
14881    cx.executor().run_until_parked();
14882
14883    cx.update_editor(|editor, _, _| {
14884        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14885        {
14886            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
14887        } else {
14888            panic!("expected completion menu to be open");
14889        }
14890    });
14891
14892    cx.update_editor(|editor, window, cx| {
14893        editor.move_page_down(&MovePageDown::default(), window, cx);
14894        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14895        {
14896            assert!(
14897                menu.selected_item == 1,
14898                "expected PageDown to select the last item from the context menu"
14899            );
14900        } else {
14901            panic!("expected completion menu to stay open after PageDown");
14902        }
14903    });
14904
14905    cx.update_editor(|editor, window, cx| {
14906        editor.move_page_up(&MovePageUp::default(), window, cx);
14907        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14908        {
14909            assert!(
14910                menu.selected_item == 0,
14911                "expected PageUp to select the first item from the context menu"
14912            );
14913        } else {
14914            panic!("expected completion menu to stay open after PageUp");
14915        }
14916    });
14917}
14918
14919#[gpui::test]
14920async fn test_as_is_completions(cx: &mut TestAppContext) {
14921    init_test(cx, |_| {});
14922    let mut cx = EditorLspTestContext::new_rust(
14923        lsp::ServerCapabilities {
14924            completion_provider: Some(lsp::CompletionOptions {
14925                ..Default::default()
14926            }),
14927            ..Default::default()
14928        },
14929        cx,
14930    )
14931    .await;
14932    cx.lsp
14933        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14934            Ok(Some(lsp::CompletionResponse::Array(vec![
14935                lsp::CompletionItem {
14936                    label: "unsafe".into(),
14937                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14938                        range: lsp::Range {
14939                            start: lsp::Position {
14940                                line: 1,
14941                                character: 2,
14942                            },
14943                            end: lsp::Position {
14944                                line: 1,
14945                                character: 3,
14946                            },
14947                        },
14948                        new_text: "unsafe".to_string(),
14949                    })),
14950                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
14951                    ..Default::default()
14952                },
14953            ])))
14954        });
14955    cx.set_state("fn a() {}\n");
14956    cx.executor().run_until_parked();
14957    cx.update_editor(|editor, window, cx| {
14958        editor.show_completions(
14959            &ShowCompletions {
14960                trigger: Some("\n".into()),
14961            },
14962            window,
14963            cx,
14964        );
14965    });
14966    cx.executor().run_until_parked();
14967
14968    cx.update_editor(|editor, window, cx| {
14969        editor.confirm_completion(&Default::default(), window, cx)
14970    });
14971    cx.executor().run_until_parked();
14972    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
14973}
14974
14975#[gpui::test]
14976async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
14977    init_test(cx, |_| {});
14978    let language =
14979        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
14980    let mut cx = EditorLspTestContext::new(
14981        language,
14982        lsp::ServerCapabilities {
14983            completion_provider: Some(lsp::CompletionOptions {
14984                ..lsp::CompletionOptions::default()
14985            }),
14986            ..lsp::ServerCapabilities::default()
14987        },
14988        cx,
14989    )
14990    .await;
14991
14992    cx.set_state(
14993        "#ifndef BAR_H
14994#define BAR_H
14995
14996#include <stdbool.h>
14997
14998int fn_branch(bool do_branch1, bool do_branch2);
14999
15000#endif // BAR_H
15001ˇ",
15002    );
15003    cx.executor().run_until_parked();
15004    cx.update_editor(|editor, window, cx| {
15005        editor.handle_input("#", window, cx);
15006    });
15007    cx.executor().run_until_parked();
15008    cx.update_editor(|editor, window, cx| {
15009        editor.handle_input("i", window, cx);
15010    });
15011    cx.executor().run_until_parked();
15012    cx.update_editor(|editor, window, cx| {
15013        editor.handle_input("n", window, cx);
15014    });
15015    cx.executor().run_until_parked();
15016    cx.assert_editor_state(
15017        "#ifndef BAR_H
15018#define BAR_H
15019
15020#include <stdbool.h>
15021
15022int fn_branch(bool do_branch1, bool do_branch2);
15023
15024#endif // BAR_H
15025#inˇ",
15026    );
15027
15028    cx.lsp
15029        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15030            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15031                is_incomplete: false,
15032                item_defaults: None,
15033                items: vec![lsp::CompletionItem {
15034                    kind: Some(lsp::CompletionItemKind::SNIPPET),
15035                    label_details: Some(lsp::CompletionItemLabelDetails {
15036                        detail: Some("header".to_string()),
15037                        description: None,
15038                    }),
15039                    label: " include".to_string(),
15040                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15041                        range: lsp::Range {
15042                            start: lsp::Position {
15043                                line: 8,
15044                                character: 1,
15045                            },
15046                            end: lsp::Position {
15047                                line: 8,
15048                                character: 1,
15049                            },
15050                        },
15051                        new_text: "include \"$0\"".to_string(),
15052                    })),
15053                    sort_text: Some("40b67681include".to_string()),
15054                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15055                    filter_text: Some("include".to_string()),
15056                    insert_text: Some("include \"$0\"".to_string()),
15057                    ..lsp::CompletionItem::default()
15058                }],
15059            })))
15060        });
15061    cx.update_editor(|editor, window, cx| {
15062        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15063    });
15064    cx.executor().run_until_parked();
15065    cx.update_editor(|editor, window, cx| {
15066        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15067    });
15068    cx.executor().run_until_parked();
15069    cx.assert_editor_state(
15070        "#ifndef BAR_H
15071#define BAR_H
15072
15073#include <stdbool.h>
15074
15075int fn_branch(bool do_branch1, bool do_branch2);
15076
15077#endif // BAR_H
15078#include \"ˇ\"",
15079    );
15080
15081    cx.lsp
15082        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15083            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15084                is_incomplete: true,
15085                item_defaults: None,
15086                items: vec![lsp::CompletionItem {
15087                    kind: Some(lsp::CompletionItemKind::FILE),
15088                    label: "AGL/".to_string(),
15089                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15090                        range: lsp::Range {
15091                            start: lsp::Position {
15092                                line: 8,
15093                                character: 10,
15094                            },
15095                            end: lsp::Position {
15096                                line: 8,
15097                                character: 11,
15098                            },
15099                        },
15100                        new_text: "AGL/".to_string(),
15101                    })),
15102                    sort_text: Some("40b67681AGL/".to_string()),
15103                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15104                    filter_text: Some("AGL/".to_string()),
15105                    insert_text: Some("AGL/".to_string()),
15106                    ..lsp::CompletionItem::default()
15107                }],
15108            })))
15109        });
15110    cx.update_editor(|editor, window, cx| {
15111        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15112    });
15113    cx.executor().run_until_parked();
15114    cx.update_editor(|editor, window, cx| {
15115        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15116    });
15117    cx.executor().run_until_parked();
15118    cx.assert_editor_state(
15119        r##"#ifndef BAR_H
15120#define BAR_H
15121
15122#include <stdbool.h>
15123
15124int fn_branch(bool do_branch1, bool do_branch2);
15125
15126#endif // BAR_H
15127#include "AGL/ˇ"##,
15128    );
15129
15130    cx.update_editor(|editor, window, cx| {
15131        editor.handle_input("\"", window, cx);
15132    });
15133    cx.executor().run_until_parked();
15134    cx.assert_editor_state(
15135        r##"#ifndef BAR_H
15136#define BAR_H
15137
15138#include <stdbool.h>
15139
15140int fn_branch(bool do_branch1, bool do_branch2);
15141
15142#endif // BAR_H
15143#include "AGL/"ˇ"##,
15144    );
15145}
15146
15147#[gpui::test]
15148async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15149    init_test(cx, |_| {});
15150
15151    let mut cx = EditorLspTestContext::new_rust(
15152        lsp::ServerCapabilities {
15153            completion_provider: Some(lsp::CompletionOptions {
15154                trigger_characters: Some(vec![".".to_string()]),
15155                resolve_provider: Some(true),
15156                ..Default::default()
15157            }),
15158            ..Default::default()
15159        },
15160        cx,
15161    )
15162    .await;
15163
15164    cx.set_state("fn main() { let a = 2ˇ; }");
15165    cx.simulate_keystroke(".");
15166    let completion_item = lsp::CompletionItem {
15167        label: "Some".into(),
15168        kind: Some(lsp::CompletionItemKind::SNIPPET),
15169        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15170        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15171            kind: lsp::MarkupKind::Markdown,
15172            value: "```rust\nSome(2)\n```".to_string(),
15173        })),
15174        deprecated: Some(false),
15175        sort_text: Some("Some".to_string()),
15176        filter_text: Some("Some".to_string()),
15177        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15178        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15179            range: lsp::Range {
15180                start: lsp::Position {
15181                    line: 0,
15182                    character: 22,
15183                },
15184                end: lsp::Position {
15185                    line: 0,
15186                    character: 22,
15187                },
15188            },
15189            new_text: "Some(2)".to_string(),
15190        })),
15191        additional_text_edits: Some(vec![lsp::TextEdit {
15192            range: lsp::Range {
15193                start: lsp::Position {
15194                    line: 0,
15195                    character: 20,
15196                },
15197                end: lsp::Position {
15198                    line: 0,
15199                    character: 22,
15200                },
15201            },
15202            new_text: "".to_string(),
15203        }]),
15204        ..Default::default()
15205    };
15206
15207    let closure_completion_item = completion_item.clone();
15208    let counter = Arc::new(AtomicUsize::new(0));
15209    let counter_clone = counter.clone();
15210    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15211        let task_completion_item = closure_completion_item.clone();
15212        counter_clone.fetch_add(1, atomic::Ordering::Release);
15213        async move {
15214            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15215                is_incomplete: true,
15216                item_defaults: None,
15217                items: vec![task_completion_item],
15218            })))
15219        }
15220    });
15221
15222    cx.condition(|editor, _| editor.context_menu_visible())
15223        .await;
15224    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15225    assert!(request.next().await.is_some());
15226    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15227
15228    cx.simulate_keystrokes("S o m");
15229    cx.condition(|editor, _| editor.context_menu_visible())
15230        .await;
15231    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15232    assert!(request.next().await.is_some());
15233    assert!(request.next().await.is_some());
15234    assert!(request.next().await.is_some());
15235    request.close();
15236    assert!(request.next().await.is_none());
15237    assert_eq!(
15238        counter.load(atomic::Ordering::Acquire),
15239        4,
15240        "With the completions menu open, only one LSP request should happen per input"
15241    );
15242}
15243
15244#[gpui::test]
15245async fn test_toggle_comment(cx: &mut TestAppContext) {
15246    init_test(cx, |_| {});
15247    let mut cx = EditorTestContext::new(cx).await;
15248    let language = Arc::new(Language::new(
15249        LanguageConfig {
15250            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15251            ..Default::default()
15252        },
15253        Some(tree_sitter_rust::LANGUAGE.into()),
15254    ));
15255    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15256
15257    // If multiple selections intersect a line, the line is only toggled once.
15258    cx.set_state(indoc! {"
15259        fn a() {
15260            «//b();
15261            ˇ»// «c();
15262            //ˇ»  d();
15263        }
15264    "});
15265
15266    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15267
15268    cx.assert_editor_state(indoc! {"
15269        fn a() {
15270            «b();
15271            c();
15272            ˇ» d();
15273        }
15274    "});
15275
15276    // The comment prefix is inserted at the same column for every line in a
15277    // selection.
15278    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15279
15280    cx.assert_editor_state(indoc! {"
15281        fn a() {
15282            // «b();
15283            // c();
15284            ˇ»//  d();
15285        }
15286    "});
15287
15288    // If a selection ends at the beginning of a line, that line is not toggled.
15289    cx.set_selections_state(indoc! {"
15290        fn a() {
15291            // b();
15292            «// c();
15293        ˇ»    //  d();
15294        }
15295    "});
15296
15297    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15298
15299    cx.assert_editor_state(indoc! {"
15300        fn a() {
15301            // b();
15302            «c();
15303        ˇ»    //  d();
15304        }
15305    "});
15306
15307    // If a selection span a single line and is empty, the line is toggled.
15308    cx.set_state(indoc! {"
15309        fn a() {
15310            a();
15311            b();
15312        ˇ
15313        }
15314    "});
15315
15316    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15317
15318    cx.assert_editor_state(indoc! {"
15319        fn a() {
15320            a();
15321            b();
15322        //•ˇ
15323        }
15324    "});
15325
15326    // If a selection span multiple lines, empty lines are not toggled.
15327    cx.set_state(indoc! {"
15328        fn a() {
15329            «a();
15330
15331            c();ˇ»
15332        }
15333    "});
15334
15335    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15336
15337    cx.assert_editor_state(indoc! {"
15338        fn a() {
15339            // «a();
15340
15341            // c();ˇ»
15342        }
15343    "});
15344
15345    // If a selection includes multiple comment prefixes, all lines are uncommented.
15346    cx.set_state(indoc! {"
15347        fn a() {
15348            «// a();
15349            /// b();
15350            //! c();ˇ»
15351        }
15352    "});
15353
15354    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15355
15356    cx.assert_editor_state(indoc! {"
15357        fn a() {
15358            «a();
15359            b();
15360            c();ˇ»
15361        }
15362    "});
15363}
15364
15365#[gpui::test]
15366async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15367    init_test(cx, |_| {});
15368    let mut cx = EditorTestContext::new(cx).await;
15369    let language = Arc::new(Language::new(
15370        LanguageConfig {
15371            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15372            ..Default::default()
15373        },
15374        Some(tree_sitter_rust::LANGUAGE.into()),
15375    ));
15376    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15377
15378    let toggle_comments = &ToggleComments {
15379        advance_downwards: false,
15380        ignore_indent: true,
15381    };
15382
15383    // If multiple selections intersect a line, the line is only toggled once.
15384    cx.set_state(indoc! {"
15385        fn a() {
15386        //    «b();
15387        //    c();
15388        //    ˇ» d();
15389        }
15390    "});
15391
15392    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15393
15394    cx.assert_editor_state(indoc! {"
15395        fn a() {
15396            «b();
15397            c();
15398            ˇ» d();
15399        }
15400    "});
15401
15402    // The comment prefix is inserted at the beginning of each line
15403    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15404
15405    cx.assert_editor_state(indoc! {"
15406        fn a() {
15407        //    «b();
15408        //    c();
15409        //    ˇ» d();
15410        }
15411    "});
15412
15413    // If a selection ends at the beginning of a line, that line is not toggled.
15414    cx.set_selections_state(indoc! {"
15415        fn a() {
15416        //    b();
15417        //    «c();
15418        ˇ»//     d();
15419        }
15420    "});
15421
15422    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15423
15424    cx.assert_editor_state(indoc! {"
15425        fn a() {
15426        //    b();
15427            «c();
15428        ˇ»//     d();
15429        }
15430    "});
15431
15432    // If a selection span a single line and is empty, the line is toggled.
15433    cx.set_state(indoc! {"
15434        fn a() {
15435            a();
15436            b();
15437        ˇ
15438        }
15439    "});
15440
15441    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15442
15443    cx.assert_editor_state(indoc! {"
15444        fn a() {
15445            a();
15446            b();
15447        //ˇ
15448        }
15449    "});
15450
15451    // If a selection span multiple lines, empty lines are not toggled.
15452    cx.set_state(indoc! {"
15453        fn a() {
15454            «a();
15455
15456            c();ˇ»
15457        }
15458    "});
15459
15460    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15461
15462    cx.assert_editor_state(indoc! {"
15463        fn a() {
15464        //    «a();
15465
15466        //    c();ˇ»
15467        }
15468    "});
15469
15470    // If a selection includes multiple comment prefixes, all lines are uncommented.
15471    cx.set_state(indoc! {"
15472        fn a() {
15473        //    «a();
15474        ///    b();
15475        //!    c();ˇ»
15476        }
15477    "});
15478
15479    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15480
15481    cx.assert_editor_state(indoc! {"
15482        fn a() {
15483            «a();
15484            b();
15485            c();ˇ»
15486        }
15487    "});
15488}
15489
15490#[gpui::test]
15491async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15492    init_test(cx, |_| {});
15493
15494    let language = Arc::new(Language::new(
15495        LanguageConfig {
15496            line_comments: vec!["// ".into()],
15497            ..Default::default()
15498        },
15499        Some(tree_sitter_rust::LANGUAGE.into()),
15500    ));
15501
15502    let mut cx = EditorTestContext::new(cx).await;
15503
15504    cx.language_registry().add(language.clone());
15505    cx.update_buffer(|buffer, cx| {
15506        buffer.set_language(Some(language), cx);
15507    });
15508
15509    let toggle_comments = &ToggleComments {
15510        advance_downwards: true,
15511        ignore_indent: false,
15512    };
15513
15514    // Single cursor on one line -> advance
15515    // Cursor moves horizontally 3 characters as well on non-blank line
15516    cx.set_state(indoc!(
15517        "fn a() {
15518             ˇdog();
15519             cat();
15520        }"
15521    ));
15522    cx.update_editor(|editor, window, cx| {
15523        editor.toggle_comments(toggle_comments, window, cx);
15524    });
15525    cx.assert_editor_state(indoc!(
15526        "fn a() {
15527             // dog();
15528             catˇ();
15529        }"
15530    ));
15531
15532    // Single selection on one line -> don't advance
15533    cx.set_state(indoc!(
15534        "fn a() {
15535             «dog()ˇ»;
15536             cat();
15537        }"
15538    ));
15539    cx.update_editor(|editor, window, cx| {
15540        editor.toggle_comments(toggle_comments, window, cx);
15541    });
15542    cx.assert_editor_state(indoc!(
15543        "fn a() {
15544             // «dog()ˇ»;
15545             cat();
15546        }"
15547    ));
15548
15549    // Multiple cursors on one line -> advance
15550    cx.set_state(indoc!(
15551        "fn a() {
15552             ˇdˇog();
15553             cat();
15554        }"
15555    ));
15556    cx.update_editor(|editor, window, cx| {
15557        editor.toggle_comments(toggle_comments, window, cx);
15558    });
15559    cx.assert_editor_state(indoc!(
15560        "fn a() {
15561             // dog();
15562             catˇ(ˇ);
15563        }"
15564    ));
15565
15566    // Multiple cursors on one line, with selection -> don't advance
15567    cx.set_state(indoc!(
15568        "fn a() {
15569             ˇdˇog«()ˇ»;
15570             cat();
15571        }"
15572    ));
15573    cx.update_editor(|editor, window, cx| {
15574        editor.toggle_comments(toggle_comments, window, cx);
15575    });
15576    cx.assert_editor_state(indoc!(
15577        "fn a() {
15578             // ˇdˇog«()ˇ»;
15579             cat();
15580        }"
15581    ));
15582
15583    // Single cursor on one line -> advance
15584    // Cursor moves to column 0 on blank line
15585    cx.set_state(indoc!(
15586        "fn a() {
15587             ˇdog();
15588
15589             cat();
15590        }"
15591    ));
15592    cx.update_editor(|editor, window, cx| {
15593        editor.toggle_comments(toggle_comments, window, cx);
15594    });
15595    cx.assert_editor_state(indoc!(
15596        "fn a() {
15597             // dog();
15598        ˇ
15599             cat();
15600        }"
15601    ));
15602
15603    // Single cursor on one line -> advance
15604    // Cursor starts and ends at column 0
15605    cx.set_state(indoc!(
15606        "fn a() {
15607         ˇ    dog();
15608             cat();
15609        }"
15610    ));
15611    cx.update_editor(|editor, window, cx| {
15612        editor.toggle_comments(toggle_comments, window, cx);
15613    });
15614    cx.assert_editor_state(indoc!(
15615        "fn a() {
15616             // dog();
15617         ˇ    cat();
15618        }"
15619    ));
15620}
15621
15622#[gpui::test]
15623async fn test_toggle_block_comment(cx: &mut TestAppContext) {
15624    init_test(cx, |_| {});
15625
15626    let mut cx = EditorTestContext::new(cx).await;
15627
15628    let html_language = Arc::new(
15629        Language::new(
15630            LanguageConfig {
15631                name: "HTML".into(),
15632                block_comment: Some(BlockCommentConfig {
15633                    start: "<!-- ".into(),
15634                    prefix: "".into(),
15635                    end: " -->".into(),
15636                    tab_size: 0,
15637                }),
15638                ..Default::default()
15639            },
15640            Some(tree_sitter_html::LANGUAGE.into()),
15641        )
15642        .with_injection_query(
15643            r#"
15644            (script_element
15645                (raw_text) @injection.content
15646                (#set! injection.language "javascript"))
15647            "#,
15648        )
15649        .unwrap(),
15650    );
15651
15652    let javascript_language = Arc::new(Language::new(
15653        LanguageConfig {
15654            name: "JavaScript".into(),
15655            line_comments: vec!["// ".into()],
15656            ..Default::default()
15657        },
15658        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15659    ));
15660
15661    cx.language_registry().add(html_language.clone());
15662    cx.language_registry().add(javascript_language);
15663    cx.update_buffer(|buffer, cx| {
15664        buffer.set_language(Some(html_language), cx);
15665    });
15666
15667    // Toggle comments for empty selections
15668    cx.set_state(
15669        &r#"
15670            <p>A</p>ˇ
15671            <p>B</p>ˇ
15672            <p>C</p>ˇ
15673        "#
15674        .unindent(),
15675    );
15676    cx.update_editor(|editor, window, cx| {
15677        editor.toggle_comments(&ToggleComments::default(), window, cx)
15678    });
15679    cx.assert_editor_state(
15680        &r#"
15681            <!-- <p>A</p>ˇ -->
15682            <!-- <p>B</p>ˇ -->
15683            <!-- <p>C</p>ˇ -->
15684        "#
15685        .unindent(),
15686    );
15687    cx.update_editor(|editor, window, cx| {
15688        editor.toggle_comments(&ToggleComments::default(), window, cx)
15689    });
15690    cx.assert_editor_state(
15691        &r#"
15692            <p>A</p>ˇ
15693            <p>B</p>ˇ
15694            <p>C</p>ˇ
15695        "#
15696        .unindent(),
15697    );
15698
15699    // Toggle comments for mixture of empty and non-empty selections, where
15700    // multiple selections occupy a given line.
15701    cx.set_state(
15702        &r#"
15703            <p>A«</p>
15704            <p>ˇ»B</p>ˇ
15705            <p>C«</p>
15706            <p>ˇ»D</p>ˇ
15707        "#
15708        .unindent(),
15709    );
15710
15711    cx.update_editor(|editor, window, cx| {
15712        editor.toggle_comments(&ToggleComments::default(), window, cx)
15713    });
15714    cx.assert_editor_state(
15715        &r#"
15716            <!-- <p>A«</p>
15717            <p>ˇ»B</p>ˇ -->
15718            <!-- <p>C«</p>
15719            <p>ˇ»D</p>ˇ -->
15720        "#
15721        .unindent(),
15722    );
15723    cx.update_editor(|editor, window, cx| {
15724        editor.toggle_comments(&ToggleComments::default(), window, cx)
15725    });
15726    cx.assert_editor_state(
15727        &r#"
15728            <p>A«</p>
15729            <p>ˇ»B</p>ˇ
15730            <p>C«</p>
15731            <p>ˇ»D</p>ˇ
15732        "#
15733        .unindent(),
15734    );
15735
15736    // Toggle comments when different languages are active for different
15737    // selections.
15738    cx.set_state(
15739        &r#"
15740            ˇ<script>
15741                ˇvar x = new Y();
15742            ˇ</script>
15743        "#
15744        .unindent(),
15745    );
15746    cx.executor().run_until_parked();
15747    cx.update_editor(|editor, window, cx| {
15748        editor.toggle_comments(&ToggleComments::default(), window, cx)
15749    });
15750    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
15751    // Uncommenting and commenting from this position brings in even more wrong artifacts.
15752    cx.assert_editor_state(
15753        &r#"
15754            <!-- ˇ<script> -->
15755                // ˇvar x = new Y();
15756            <!-- ˇ</script> -->
15757        "#
15758        .unindent(),
15759    );
15760}
15761
15762#[gpui::test]
15763fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
15764    init_test(cx, |_| {});
15765
15766    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15767    let multibuffer = cx.new(|cx| {
15768        let mut multibuffer = MultiBuffer::new(ReadWrite);
15769        multibuffer.push_excerpts(
15770            buffer.clone(),
15771            [
15772                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
15773                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
15774            ],
15775            cx,
15776        );
15777        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
15778        multibuffer
15779    });
15780
15781    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15782    editor.update_in(cx, |editor, window, cx| {
15783        assert_eq!(editor.text(cx), "aaaa\nbbbb");
15784        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15785            s.select_ranges([
15786                Point::new(0, 0)..Point::new(0, 0),
15787                Point::new(1, 0)..Point::new(1, 0),
15788            ])
15789        });
15790
15791        editor.handle_input("X", window, cx);
15792        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
15793        assert_eq!(
15794            editor.selections.ranges(cx),
15795            [
15796                Point::new(0, 1)..Point::new(0, 1),
15797                Point::new(1, 1)..Point::new(1, 1),
15798            ]
15799        );
15800
15801        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
15802        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15803            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
15804        });
15805        editor.backspace(&Default::default(), window, cx);
15806        assert_eq!(editor.text(cx), "Xa\nbbb");
15807        assert_eq!(
15808            editor.selections.ranges(cx),
15809            [Point::new(1, 0)..Point::new(1, 0)]
15810        );
15811
15812        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15813            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
15814        });
15815        editor.backspace(&Default::default(), window, cx);
15816        assert_eq!(editor.text(cx), "X\nbb");
15817        assert_eq!(
15818            editor.selections.ranges(cx),
15819            [Point::new(0, 1)..Point::new(0, 1)]
15820        );
15821    });
15822}
15823
15824#[gpui::test]
15825fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
15826    init_test(cx, |_| {});
15827
15828    let markers = vec![('[', ']').into(), ('(', ')').into()];
15829    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
15830        indoc! {"
15831            [aaaa
15832            (bbbb]
15833            cccc)",
15834        },
15835        markers.clone(),
15836    );
15837    let excerpt_ranges = markers.into_iter().map(|marker| {
15838        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
15839        ExcerptRange::new(context)
15840    });
15841    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
15842    let multibuffer = cx.new(|cx| {
15843        let mut multibuffer = MultiBuffer::new(ReadWrite);
15844        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
15845        multibuffer
15846    });
15847
15848    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15849    editor.update_in(cx, |editor, window, cx| {
15850        let (expected_text, selection_ranges) = marked_text_ranges(
15851            indoc! {"
15852                aaaa
15853                bˇbbb
15854                bˇbbˇb
15855                cccc"
15856            },
15857            true,
15858        );
15859        assert_eq!(editor.text(cx), expected_text);
15860        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15861            s.select_ranges(selection_ranges)
15862        });
15863
15864        editor.handle_input("X", window, cx);
15865
15866        let (expected_text, expected_selections) = marked_text_ranges(
15867            indoc! {"
15868                aaaa
15869                bXˇbbXb
15870                bXˇbbXˇb
15871                cccc"
15872            },
15873            false,
15874        );
15875        assert_eq!(editor.text(cx), expected_text);
15876        assert_eq!(editor.selections.ranges(cx), expected_selections);
15877
15878        editor.newline(&Newline, window, cx);
15879        let (expected_text, expected_selections) = marked_text_ranges(
15880            indoc! {"
15881                aaaa
15882                bX
15883                ˇbbX
15884                b
15885                bX
15886                ˇbbX
15887                ˇb
15888                cccc"
15889            },
15890            false,
15891        );
15892        assert_eq!(editor.text(cx), expected_text);
15893        assert_eq!(editor.selections.ranges(cx), expected_selections);
15894    });
15895}
15896
15897#[gpui::test]
15898fn test_refresh_selections(cx: &mut TestAppContext) {
15899    init_test(cx, |_| {});
15900
15901    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15902    let mut excerpt1_id = None;
15903    let multibuffer = cx.new(|cx| {
15904        let mut multibuffer = MultiBuffer::new(ReadWrite);
15905        excerpt1_id = multibuffer
15906            .push_excerpts(
15907                buffer.clone(),
15908                [
15909                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15910                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15911                ],
15912                cx,
15913            )
15914            .into_iter()
15915            .next();
15916        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15917        multibuffer
15918    });
15919
15920    let editor = cx.add_window(|window, cx| {
15921        let mut editor = build_editor(multibuffer.clone(), window, cx);
15922        let snapshot = editor.snapshot(window, cx);
15923        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15924            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
15925        });
15926        editor.begin_selection(
15927            Point::new(2, 1).to_display_point(&snapshot),
15928            true,
15929            1,
15930            window,
15931            cx,
15932        );
15933        assert_eq!(
15934            editor.selections.ranges(cx),
15935            [
15936                Point::new(1, 3)..Point::new(1, 3),
15937                Point::new(2, 1)..Point::new(2, 1),
15938            ]
15939        );
15940        editor
15941    });
15942
15943    // Refreshing selections is a no-op when excerpts haven't changed.
15944    _ = editor.update(cx, |editor, window, cx| {
15945        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15946        assert_eq!(
15947            editor.selections.ranges(cx),
15948            [
15949                Point::new(1, 3)..Point::new(1, 3),
15950                Point::new(2, 1)..Point::new(2, 1),
15951            ]
15952        );
15953    });
15954
15955    multibuffer.update(cx, |multibuffer, cx| {
15956        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
15957    });
15958    _ = editor.update(cx, |editor, window, cx| {
15959        // Removing an excerpt causes the first selection to become degenerate.
15960        assert_eq!(
15961            editor.selections.ranges(cx),
15962            [
15963                Point::new(0, 0)..Point::new(0, 0),
15964                Point::new(0, 1)..Point::new(0, 1)
15965            ]
15966        );
15967
15968        // Refreshing selections will relocate the first selection to the original buffer
15969        // location.
15970        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15971        assert_eq!(
15972            editor.selections.ranges(cx),
15973            [
15974                Point::new(0, 1)..Point::new(0, 1),
15975                Point::new(0, 3)..Point::new(0, 3)
15976            ]
15977        );
15978        assert!(editor.selections.pending_anchor().is_some());
15979    });
15980}
15981
15982#[gpui::test]
15983fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
15984    init_test(cx, |_| {});
15985
15986    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15987    let mut excerpt1_id = None;
15988    let multibuffer = cx.new(|cx| {
15989        let mut multibuffer = MultiBuffer::new(ReadWrite);
15990        excerpt1_id = multibuffer
15991            .push_excerpts(
15992                buffer.clone(),
15993                [
15994                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15995                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15996                ],
15997                cx,
15998            )
15999            .into_iter()
16000            .next();
16001        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16002        multibuffer
16003    });
16004
16005    let editor = cx.add_window(|window, cx| {
16006        let mut editor = build_editor(multibuffer.clone(), window, cx);
16007        let snapshot = editor.snapshot(window, cx);
16008        editor.begin_selection(
16009            Point::new(1, 3).to_display_point(&snapshot),
16010            false,
16011            1,
16012            window,
16013            cx,
16014        );
16015        assert_eq!(
16016            editor.selections.ranges(cx),
16017            [Point::new(1, 3)..Point::new(1, 3)]
16018        );
16019        editor
16020    });
16021
16022    multibuffer.update(cx, |multibuffer, cx| {
16023        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16024    });
16025    _ = editor.update(cx, |editor, window, cx| {
16026        assert_eq!(
16027            editor.selections.ranges(cx),
16028            [Point::new(0, 0)..Point::new(0, 0)]
16029        );
16030
16031        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
16032        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16033        assert_eq!(
16034            editor.selections.ranges(cx),
16035            [Point::new(0, 3)..Point::new(0, 3)]
16036        );
16037        assert!(editor.selections.pending_anchor().is_some());
16038    });
16039}
16040
16041#[gpui::test]
16042async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
16043    init_test(cx, |_| {});
16044
16045    let language = Arc::new(
16046        Language::new(
16047            LanguageConfig {
16048                brackets: BracketPairConfig {
16049                    pairs: vec![
16050                        BracketPair {
16051                            start: "{".to_string(),
16052                            end: "}".to_string(),
16053                            close: true,
16054                            surround: true,
16055                            newline: true,
16056                        },
16057                        BracketPair {
16058                            start: "/* ".to_string(),
16059                            end: " */".to_string(),
16060                            close: true,
16061                            surround: true,
16062                            newline: true,
16063                        },
16064                    ],
16065                    ..Default::default()
16066                },
16067                ..Default::default()
16068            },
16069            Some(tree_sitter_rust::LANGUAGE.into()),
16070        )
16071        .with_indents_query("")
16072        .unwrap(),
16073    );
16074
16075    let text = concat!(
16076        "{   }\n",     //
16077        "  x\n",       //
16078        "  /*   */\n", //
16079        "x\n",         //
16080        "{{} }\n",     //
16081    );
16082
16083    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16084    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16085    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16086    editor
16087        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16088        .await;
16089
16090    editor.update_in(cx, |editor, window, cx| {
16091        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16092            s.select_display_ranges([
16093                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16094                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16095                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16096            ])
16097        });
16098        editor.newline(&Newline, window, cx);
16099
16100        assert_eq!(
16101            editor.buffer().read(cx).read(cx).text(),
16102            concat!(
16103                "{ \n",    // Suppress rustfmt
16104                "\n",      //
16105                "}\n",     //
16106                "  x\n",   //
16107                "  /* \n", //
16108                "  \n",    //
16109                "  */\n",  //
16110                "x\n",     //
16111                "{{} \n",  //
16112                "}\n",     //
16113            )
16114        );
16115    });
16116}
16117
16118#[gpui::test]
16119fn test_highlighted_ranges(cx: &mut TestAppContext) {
16120    init_test(cx, |_| {});
16121
16122    let editor = cx.add_window(|window, cx| {
16123        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16124        build_editor(buffer, window, cx)
16125    });
16126
16127    _ = editor.update(cx, |editor, window, cx| {
16128        struct Type1;
16129        struct Type2;
16130
16131        let buffer = editor.buffer.read(cx).snapshot(cx);
16132
16133        let anchor_range =
16134            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16135
16136        editor.highlight_background::<Type1>(
16137            &[
16138                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16139                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16140                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16141                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16142            ],
16143            |_| Hsla::red(),
16144            cx,
16145        );
16146        editor.highlight_background::<Type2>(
16147            &[
16148                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16149                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16150                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16151                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16152            ],
16153            |_| Hsla::green(),
16154            cx,
16155        );
16156
16157        let snapshot = editor.snapshot(window, cx);
16158        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16159            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16160            &snapshot,
16161            cx.theme(),
16162        );
16163        assert_eq!(
16164            highlighted_ranges,
16165            &[
16166                (
16167                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16168                    Hsla::green(),
16169                ),
16170                (
16171                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16172                    Hsla::red(),
16173                ),
16174                (
16175                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16176                    Hsla::green(),
16177                ),
16178                (
16179                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16180                    Hsla::red(),
16181                ),
16182            ]
16183        );
16184        assert_eq!(
16185            editor.sorted_background_highlights_in_range(
16186                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16187                &snapshot,
16188                cx.theme(),
16189            ),
16190            &[(
16191                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16192                Hsla::red(),
16193            )]
16194        );
16195    });
16196}
16197
16198#[gpui::test]
16199async fn test_following(cx: &mut TestAppContext) {
16200    init_test(cx, |_| {});
16201
16202    let fs = FakeFs::new(cx.executor());
16203    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16204
16205    let buffer = project.update(cx, |project, cx| {
16206        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16207        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16208    });
16209    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16210    let follower = cx.update(|cx| {
16211        cx.open_window(
16212            WindowOptions {
16213                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16214                    gpui::Point::new(px(0.), px(0.)),
16215                    gpui::Point::new(px(10.), px(80.)),
16216                ))),
16217                ..Default::default()
16218            },
16219            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16220        )
16221        .unwrap()
16222    });
16223
16224    let is_still_following = Rc::new(RefCell::new(true));
16225    let follower_edit_event_count = Rc::new(RefCell::new(0));
16226    let pending_update = Rc::new(RefCell::new(None));
16227    let leader_entity = leader.root(cx).unwrap();
16228    let follower_entity = follower.root(cx).unwrap();
16229    _ = follower.update(cx, {
16230        let update = pending_update.clone();
16231        let is_still_following = is_still_following.clone();
16232        let follower_edit_event_count = follower_edit_event_count.clone();
16233        |_, window, cx| {
16234            cx.subscribe_in(
16235                &leader_entity,
16236                window,
16237                move |_, leader, event, window, cx| {
16238                    leader.read(cx).add_event_to_update_proto(
16239                        event,
16240                        &mut update.borrow_mut(),
16241                        window,
16242                        cx,
16243                    );
16244                },
16245            )
16246            .detach();
16247
16248            cx.subscribe_in(
16249                &follower_entity,
16250                window,
16251                move |_, _, event: &EditorEvent, _window, _cx| {
16252                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16253                        *is_still_following.borrow_mut() = false;
16254                    }
16255
16256                    if let EditorEvent::BufferEdited = event {
16257                        *follower_edit_event_count.borrow_mut() += 1;
16258                    }
16259                },
16260            )
16261            .detach();
16262        }
16263    });
16264
16265    // Update the selections only
16266    _ = leader.update(cx, |leader, window, cx| {
16267        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16268            s.select_ranges([1..1])
16269        });
16270    });
16271    follower
16272        .update(cx, |follower, window, cx| {
16273            follower.apply_update_proto(
16274                &project,
16275                pending_update.borrow_mut().take().unwrap(),
16276                window,
16277                cx,
16278            )
16279        })
16280        .unwrap()
16281        .await
16282        .unwrap();
16283    _ = follower.update(cx, |follower, _, cx| {
16284        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
16285    });
16286    assert!(*is_still_following.borrow());
16287    assert_eq!(*follower_edit_event_count.borrow(), 0);
16288
16289    // Update the scroll position only
16290    _ = leader.update(cx, |leader, window, cx| {
16291        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16292    });
16293    follower
16294        .update(cx, |follower, window, cx| {
16295            follower.apply_update_proto(
16296                &project,
16297                pending_update.borrow_mut().take().unwrap(),
16298                window,
16299                cx,
16300            )
16301        })
16302        .unwrap()
16303        .await
16304        .unwrap();
16305    assert_eq!(
16306        follower
16307            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16308            .unwrap(),
16309        gpui::Point::new(1.5, 3.5)
16310    );
16311    assert!(*is_still_following.borrow());
16312    assert_eq!(*follower_edit_event_count.borrow(), 0);
16313
16314    // Update the selections and scroll position. The follower's scroll position is updated
16315    // via autoscroll, not via the leader's exact scroll position.
16316    _ = leader.update(cx, |leader, window, cx| {
16317        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16318            s.select_ranges([0..0])
16319        });
16320        leader.request_autoscroll(Autoscroll::newest(), cx);
16321        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16322    });
16323    follower
16324        .update(cx, |follower, window, cx| {
16325            follower.apply_update_proto(
16326                &project,
16327                pending_update.borrow_mut().take().unwrap(),
16328                window,
16329                cx,
16330            )
16331        })
16332        .unwrap()
16333        .await
16334        .unwrap();
16335    _ = follower.update(cx, |follower, _, cx| {
16336        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16337        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
16338    });
16339    assert!(*is_still_following.borrow());
16340
16341    // Creating a pending selection that precedes another selection
16342    _ = leader.update(cx, |leader, window, cx| {
16343        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16344            s.select_ranges([1..1])
16345        });
16346        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16347    });
16348    follower
16349        .update(cx, |follower, window, cx| {
16350            follower.apply_update_proto(
16351                &project,
16352                pending_update.borrow_mut().take().unwrap(),
16353                window,
16354                cx,
16355            )
16356        })
16357        .unwrap()
16358        .await
16359        .unwrap();
16360    _ = follower.update(cx, |follower, _, cx| {
16361        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
16362    });
16363    assert!(*is_still_following.borrow());
16364
16365    // Extend the pending selection so that it surrounds another selection
16366    _ = leader.update(cx, |leader, window, cx| {
16367        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16368    });
16369    follower
16370        .update(cx, |follower, window, cx| {
16371            follower.apply_update_proto(
16372                &project,
16373                pending_update.borrow_mut().take().unwrap(),
16374                window,
16375                cx,
16376            )
16377        })
16378        .unwrap()
16379        .await
16380        .unwrap();
16381    _ = follower.update(cx, |follower, _, cx| {
16382        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
16383    });
16384
16385    // Scrolling locally breaks the follow
16386    _ = follower.update(cx, |follower, window, cx| {
16387        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16388        follower.set_scroll_anchor(
16389            ScrollAnchor {
16390                anchor: top_anchor,
16391                offset: gpui::Point::new(0.0, 0.5),
16392            },
16393            window,
16394            cx,
16395        );
16396    });
16397    assert!(!(*is_still_following.borrow()));
16398}
16399
16400#[gpui::test]
16401async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16402    init_test(cx, |_| {});
16403
16404    let fs = FakeFs::new(cx.executor());
16405    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16406    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16407    let pane = workspace
16408        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16409        .unwrap();
16410
16411    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16412
16413    let leader = pane.update_in(cx, |_, window, cx| {
16414        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16415        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16416    });
16417
16418    // Start following the editor when it has no excerpts.
16419    let mut state_message =
16420        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16421    let workspace_entity = workspace.root(cx).unwrap();
16422    let follower_1 = cx
16423        .update_window(*workspace.deref(), |_, window, cx| {
16424            Editor::from_state_proto(
16425                workspace_entity,
16426                ViewId {
16427                    creator: CollaboratorId::PeerId(PeerId::default()),
16428                    id: 0,
16429                },
16430                &mut state_message,
16431                window,
16432                cx,
16433            )
16434        })
16435        .unwrap()
16436        .unwrap()
16437        .await
16438        .unwrap();
16439
16440    let update_message = Rc::new(RefCell::new(None));
16441    follower_1.update_in(cx, {
16442        let update = update_message.clone();
16443        |_, window, cx| {
16444            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16445                leader.read(cx).add_event_to_update_proto(
16446                    event,
16447                    &mut update.borrow_mut(),
16448                    window,
16449                    cx,
16450                );
16451            })
16452            .detach();
16453        }
16454    });
16455
16456    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16457        (
16458            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16459            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16460        )
16461    });
16462
16463    // Insert some excerpts.
16464    leader.update(cx, |leader, cx| {
16465        leader.buffer.update(cx, |multibuffer, cx| {
16466            multibuffer.set_excerpts_for_path(
16467                PathKey::namespaced(1, rel_path("b.txt").into_arc()),
16468                buffer_1.clone(),
16469                vec![
16470                    Point::row_range(0..3),
16471                    Point::row_range(1..6),
16472                    Point::row_range(12..15),
16473                ],
16474                0,
16475                cx,
16476            );
16477            multibuffer.set_excerpts_for_path(
16478                PathKey::namespaced(1, rel_path("a.txt").into_arc()),
16479                buffer_2.clone(),
16480                vec![Point::row_range(0..6), Point::row_range(8..12)],
16481                0,
16482                cx,
16483            );
16484        });
16485    });
16486
16487    // Apply the update of adding the excerpts.
16488    follower_1
16489        .update_in(cx, |follower, window, cx| {
16490            follower.apply_update_proto(
16491                &project,
16492                update_message.borrow().clone().unwrap(),
16493                window,
16494                cx,
16495            )
16496        })
16497        .await
16498        .unwrap();
16499    assert_eq!(
16500        follower_1.update(cx, |editor, cx| editor.text(cx)),
16501        leader.update(cx, |editor, cx| editor.text(cx))
16502    );
16503    update_message.borrow_mut().take();
16504
16505    // Start following separately after it already has excerpts.
16506    let mut state_message =
16507        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16508    let workspace_entity = workspace.root(cx).unwrap();
16509    let follower_2 = cx
16510        .update_window(*workspace.deref(), |_, window, cx| {
16511            Editor::from_state_proto(
16512                workspace_entity,
16513                ViewId {
16514                    creator: CollaboratorId::PeerId(PeerId::default()),
16515                    id: 0,
16516                },
16517                &mut state_message,
16518                window,
16519                cx,
16520            )
16521        })
16522        .unwrap()
16523        .unwrap()
16524        .await
16525        .unwrap();
16526    assert_eq!(
16527        follower_2.update(cx, |editor, cx| editor.text(cx)),
16528        leader.update(cx, |editor, cx| editor.text(cx))
16529    );
16530
16531    // Remove some excerpts.
16532    leader.update(cx, |leader, cx| {
16533        leader.buffer.update(cx, |multibuffer, cx| {
16534            let excerpt_ids = multibuffer.excerpt_ids();
16535            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16536            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16537        });
16538    });
16539
16540    // Apply the update of removing the excerpts.
16541    follower_1
16542        .update_in(cx, |follower, window, cx| {
16543            follower.apply_update_proto(
16544                &project,
16545                update_message.borrow().clone().unwrap(),
16546                window,
16547                cx,
16548            )
16549        })
16550        .await
16551        .unwrap();
16552    follower_2
16553        .update_in(cx, |follower, window, cx| {
16554            follower.apply_update_proto(
16555                &project,
16556                update_message.borrow().clone().unwrap(),
16557                window,
16558                cx,
16559            )
16560        })
16561        .await
16562        .unwrap();
16563    update_message.borrow_mut().take();
16564    assert_eq!(
16565        follower_1.update(cx, |editor, cx| editor.text(cx)),
16566        leader.update(cx, |editor, cx| editor.text(cx))
16567    );
16568}
16569
16570#[gpui::test]
16571async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16572    init_test(cx, |_| {});
16573
16574    let mut cx = EditorTestContext::new(cx).await;
16575    let lsp_store =
16576        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
16577
16578    cx.set_state(indoc! {"
16579        ˇfn func(abc def: i32) -> u32 {
16580        }
16581    "});
16582
16583    cx.update(|_, cx| {
16584        lsp_store.update(cx, |lsp_store, cx| {
16585            lsp_store
16586                .update_diagnostics(
16587                    LanguageServerId(0),
16588                    lsp::PublishDiagnosticsParams {
16589                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
16590                        version: None,
16591                        diagnostics: vec![
16592                            lsp::Diagnostic {
16593                                range: lsp::Range::new(
16594                                    lsp::Position::new(0, 11),
16595                                    lsp::Position::new(0, 12),
16596                                ),
16597                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16598                                ..Default::default()
16599                            },
16600                            lsp::Diagnostic {
16601                                range: lsp::Range::new(
16602                                    lsp::Position::new(0, 12),
16603                                    lsp::Position::new(0, 15),
16604                                ),
16605                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16606                                ..Default::default()
16607                            },
16608                            lsp::Diagnostic {
16609                                range: lsp::Range::new(
16610                                    lsp::Position::new(0, 25),
16611                                    lsp::Position::new(0, 28),
16612                                ),
16613                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16614                                ..Default::default()
16615                            },
16616                        ],
16617                    },
16618                    None,
16619                    DiagnosticSourceKind::Pushed,
16620                    &[],
16621                    cx,
16622                )
16623                .unwrap()
16624        });
16625    });
16626
16627    executor.run_until_parked();
16628
16629    cx.update_editor(|editor, window, cx| {
16630        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16631    });
16632
16633    cx.assert_editor_state(indoc! {"
16634        fn func(abc def: i32) -> ˇu32 {
16635        }
16636    "});
16637
16638    cx.update_editor(|editor, window, cx| {
16639        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16640    });
16641
16642    cx.assert_editor_state(indoc! {"
16643        fn func(abc ˇdef: i32) -> u32 {
16644        }
16645    "});
16646
16647    cx.update_editor(|editor, window, cx| {
16648        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16649    });
16650
16651    cx.assert_editor_state(indoc! {"
16652        fn func(abcˇ def: i32) -> u32 {
16653        }
16654    "});
16655
16656    cx.update_editor(|editor, window, cx| {
16657        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16658    });
16659
16660    cx.assert_editor_state(indoc! {"
16661        fn func(abc def: i32) -> ˇu32 {
16662        }
16663    "});
16664}
16665
16666#[gpui::test]
16667async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16668    init_test(cx, |_| {});
16669
16670    let mut cx = EditorTestContext::new(cx).await;
16671
16672    let diff_base = r#"
16673        use some::mod;
16674
16675        const A: u32 = 42;
16676
16677        fn main() {
16678            println!("hello");
16679
16680            println!("world");
16681        }
16682        "#
16683    .unindent();
16684
16685    // Edits are modified, removed, modified, added
16686    cx.set_state(
16687        &r#"
16688        use some::modified;
16689
16690        ˇ
16691        fn main() {
16692            println!("hello there");
16693
16694            println!("around the");
16695            println!("world");
16696        }
16697        "#
16698        .unindent(),
16699    );
16700
16701    cx.set_head_text(&diff_base);
16702    executor.run_until_parked();
16703
16704    cx.update_editor(|editor, window, cx| {
16705        //Wrap around the bottom of the buffer
16706        for _ in 0..3 {
16707            editor.go_to_next_hunk(&GoToHunk, window, cx);
16708        }
16709    });
16710
16711    cx.assert_editor_state(
16712        &r#"
16713        ˇuse some::modified;
16714
16715
16716        fn main() {
16717            println!("hello there");
16718
16719            println!("around the");
16720            println!("world");
16721        }
16722        "#
16723        .unindent(),
16724    );
16725
16726    cx.update_editor(|editor, window, cx| {
16727        //Wrap around the top of the buffer
16728        for _ in 0..2 {
16729            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16730        }
16731    });
16732
16733    cx.assert_editor_state(
16734        &r#"
16735        use some::modified;
16736
16737
16738        fn main() {
16739        ˇ    println!("hello there");
16740
16741            println!("around the");
16742            println!("world");
16743        }
16744        "#
16745        .unindent(),
16746    );
16747
16748    cx.update_editor(|editor, window, cx| {
16749        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16750    });
16751
16752    cx.assert_editor_state(
16753        &r#"
16754        use some::modified;
16755
16756        ˇ
16757        fn main() {
16758            println!("hello there");
16759
16760            println!("around the");
16761            println!("world");
16762        }
16763        "#
16764        .unindent(),
16765    );
16766
16767    cx.update_editor(|editor, window, cx| {
16768        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16769    });
16770
16771    cx.assert_editor_state(
16772        &r#"
16773        ˇuse some::modified;
16774
16775
16776        fn main() {
16777            println!("hello there");
16778
16779            println!("around the");
16780            println!("world");
16781        }
16782        "#
16783        .unindent(),
16784    );
16785
16786    cx.update_editor(|editor, window, cx| {
16787        for _ in 0..2 {
16788            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16789        }
16790    });
16791
16792    cx.assert_editor_state(
16793        &r#"
16794        use some::modified;
16795
16796
16797        fn main() {
16798        ˇ    println!("hello there");
16799
16800            println!("around the");
16801            println!("world");
16802        }
16803        "#
16804        .unindent(),
16805    );
16806
16807    cx.update_editor(|editor, window, cx| {
16808        editor.fold(&Fold, window, cx);
16809    });
16810
16811    cx.update_editor(|editor, window, cx| {
16812        editor.go_to_next_hunk(&GoToHunk, window, cx);
16813    });
16814
16815    cx.assert_editor_state(
16816        &r#"
16817        ˇuse some::modified;
16818
16819
16820        fn main() {
16821            println!("hello there");
16822
16823            println!("around the");
16824            println!("world");
16825        }
16826        "#
16827        .unindent(),
16828    );
16829}
16830
16831#[test]
16832fn test_split_words() {
16833    fn split(text: &str) -> Vec<&str> {
16834        split_words(text).collect()
16835    }
16836
16837    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
16838    assert_eq!(split("hello_world"), &["hello_", "world"]);
16839    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
16840    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
16841    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
16842    assert_eq!(split("helloworld"), &["helloworld"]);
16843
16844    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
16845}
16846
16847#[gpui::test]
16848async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
16849    init_test(cx, |_| {});
16850
16851    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
16852    let mut assert = |before, after| {
16853        let _state_context = cx.set_state(before);
16854        cx.run_until_parked();
16855        cx.update_editor(|editor, window, cx| {
16856            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
16857        });
16858        cx.run_until_parked();
16859        cx.assert_editor_state(after);
16860    };
16861
16862    // Outside bracket jumps to outside of matching bracket
16863    assert("console.logˇ(var);", "console.log(var)ˇ;");
16864    assert("console.log(var)ˇ;", "console.logˇ(var);");
16865
16866    // Inside bracket jumps to inside of matching bracket
16867    assert("console.log(ˇvar);", "console.log(varˇ);");
16868    assert("console.log(varˇ);", "console.log(ˇvar);");
16869
16870    // When outside a bracket and inside, favor jumping to the inside bracket
16871    assert(
16872        "console.log('foo', [1, 2, 3]ˇ);",
16873        "console.log(ˇ'foo', [1, 2, 3]);",
16874    );
16875    assert(
16876        "console.log(ˇ'foo', [1, 2, 3]);",
16877        "console.log('foo', [1, 2, 3]ˇ);",
16878    );
16879
16880    // Bias forward if two options are equally likely
16881    assert(
16882        "let result = curried_fun()ˇ();",
16883        "let result = curried_fun()()ˇ;",
16884    );
16885
16886    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
16887    assert(
16888        indoc! {"
16889            function test() {
16890                console.log('test')ˇ
16891            }"},
16892        indoc! {"
16893            function test() {
16894                console.logˇ('test')
16895            }"},
16896    );
16897}
16898
16899#[gpui::test]
16900async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
16901    init_test(cx, |_| {});
16902
16903    let fs = FakeFs::new(cx.executor());
16904    fs.insert_tree(
16905        path!("/a"),
16906        json!({
16907            "main.rs": "fn main() { let a = 5; }",
16908            "other.rs": "// Test file",
16909        }),
16910    )
16911    .await;
16912    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16913
16914    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16915    language_registry.add(Arc::new(Language::new(
16916        LanguageConfig {
16917            name: "Rust".into(),
16918            matcher: LanguageMatcher {
16919                path_suffixes: vec!["rs".to_string()],
16920                ..Default::default()
16921            },
16922            brackets: BracketPairConfig {
16923                pairs: vec![BracketPair {
16924                    start: "{".to_string(),
16925                    end: "}".to_string(),
16926                    close: true,
16927                    surround: true,
16928                    newline: true,
16929                }],
16930                disabled_scopes_by_bracket_ix: Vec::new(),
16931            },
16932            ..Default::default()
16933        },
16934        Some(tree_sitter_rust::LANGUAGE.into()),
16935    )));
16936    let mut fake_servers = language_registry.register_fake_lsp(
16937        "Rust",
16938        FakeLspAdapter {
16939            capabilities: lsp::ServerCapabilities {
16940                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
16941                    first_trigger_character: "{".to_string(),
16942                    more_trigger_character: None,
16943                }),
16944                ..Default::default()
16945            },
16946            ..Default::default()
16947        },
16948    );
16949
16950    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16951
16952    let cx = &mut VisualTestContext::from_window(*workspace, cx);
16953
16954    let worktree_id = workspace
16955        .update(cx, |workspace, _, cx| {
16956            workspace.project().update(cx, |project, cx| {
16957                project.worktrees(cx).next().unwrap().read(cx).id()
16958            })
16959        })
16960        .unwrap();
16961
16962    let buffer = project
16963        .update(cx, |project, cx| {
16964            project.open_local_buffer(path!("/a/main.rs"), cx)
16965        })
16966        .await
16967        .unwrap();
16968    let editor_handle = workspace
16969        .update(cx, |workspace, window, cx| {
16970            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
16971        })
16972        .unwrap()
16973        .await
16974        .unwrap()
16975        .downcast::<Editor>()
16976        .unwrap();
16977
16978    cx.executor().start_waiting();
16979    let fake_server = fake_servers.next().await.unwrap();
16980
16981    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
16982        |params, _| async move {
16983            assert_eq!(
16984                params.text_document_position.text_document.uri,
16985                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
16986            );
16987            assert_eq!(
16988                params.text_document_position.position,
16989                lsp::Position::new(0, 21),
16990            );
16991
16992            Ok(Some(vec![lsp::TextEdit {
16993                new_text: "]".to_string(),
16994                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16995            }]))
16996        },
16997    );
16998
16999    editor_handle.update_in(cx, |editor, window, cx| {
17000        window.focus(&editor.focus_handle(cx));
17001        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17002            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
17003        });
17004        editor.handle_input("{", window, cx);
17005    });
17006
17007    cx.executor().run_until_parked();
17008
17009    buffer.update(cx, |buffer, _| {
17010        assert_eq!(
17011            buffer.text(),
17012            "fn main() { let a = {5}; }",
17013            "No extra braces from on type formatting should appear in the buffer"
17014        )
17015    });
17016}
17017
17018#[gpui::test(iterations = 20, seeds(31))]
17019async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
17020    init_test(cx, |_| {});
17021
17022    let mut cx = EditorLspTestContext::new_rust(
17023        lsp::ServerCapabilities {
17024            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17025                first_trigger_character: ".".to_string(),
17026                more_trigger_character: None,
17027            }),
17028            ..Default::default()
17029        },
17030        cx,
17031    )
17032    .await;
17033
17034    cx.update_buffer(|buffer, _| {
17035        // This causes autoindent to be async.
17036        buffer.set_sync_parse_timeout(Duration::ZERO)
17037    });
17038
17039    cx.set_state("fn c() {\n    d()ˇ\n}\n");
17040    cx.simulate_keystroke("\n");
17041    cx.run_until_parked();
17042
17043    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
17044    let mut request =
17045        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
17046            let buffer_cloned = buffer_cloned.clone();
17047            async move {
17048                buffer_cloned.update(&mut cx, |buffer, _| {
17049                    assert_eq!(
17050                        buffer.text(),
17051                        "fn c() {\n    d()\n        .\n}\n",
17052                        "OnTypeFormatting should triggered after autoindent applied"
17053                    )
17054                })?;
17055
17056                Ok(Some(vec![]))
17057            }
17058        });
17059
17060    cx.simulate_keystroke(".");
17061    cx.run_until_parked();
17062
17063    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
17064    assert!(request.next().await.is_some());
17065    request.close();
17066    assert!(request.next().await.is_none());
17067}
17068
17069#[gpui::test]
17070async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17071    init_test(cx, |_| {});
17072
17073    let fs = FakeFs::new(cx.executor());
17074    fs.insert_tree(
17075        path!("/a"),
17076        json!({
17077            "main.rs": "fn main() { let a = 5; }",
17078            "other.rs": "// Test file",
17079        }),
17080    )
17081    .await;
17082
17083    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17084
17085    let server_restarts = Arc::new(AtomicUsize::new(0));
17086    let closure_restarts = Arc::clone(&server_restarts);
17087    let language_server_name = "test language server";
17088    let language_name: LanguageName = "Rust".into();
17089
17090    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17091    language_registry.add(Arc::new(Language::new(
17092        LanguageConfig {
17093            name: language_name.clone(),
17094            matcher: LanguageMatcher {
17095                path_suffixes: vec!["rs".to_string()],
17096                ..Default::default()
17097            },
17098            ..Default::default()
17099        },
17100        Some(tree_sitter_rust::LANGUAGE.into()),
17101    )));
17102    let mut fake_servers = language_registry.register_fake_lsp(
17103        "Rust",
17104        FakeLspAdapter {
17105            name: language_server_name,
17106            initialization_options: Some(json!({
17107                "testOptionValue": true
17108            })),
17109            initializer: Some(Box::new(move |fake_server| {
17110                let task_restarts = Arc::clone(&closure_restarts);
17111                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17112                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17113                    futures::future::ready(Ok(()))
17114                });
17115            })),
17116            ..Default::default()
17117        },
17118    );
17119
17120    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17121    let _buffer = project
17122        .update(cx, |project, cx| {
17123            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17124        })
17125        .await
17126        .unwrap();
17127    let _fake_server = fake_servers.next().await.unwrap();
17128    update_test_language_settings(cx, |language_settings| {
17129        language_settings.languages.0.insert(
17130            language_name.clone().0,
17131            LanguageSettingsContent {
17132                tab_size: NonZeroU32::new(8),
17133                ..Default::default()
17134            },
17135        );
17136    });
17137    cx.executor().run_until_parked();
17138    assert_eq!(
17139        server_restarts.load(atomic::Ordering::Acquire),
17140        0,
17141        "Should not restart LSP server on an unrelated change"
17142    );
17143
17144    update_test_project_settings(cx, |project_settings| {
17145        project_settings.lsp.insert(
17146            "Some other server name".into(),
17147            LspSettings {
17148                binary: None,
17149                settings: None,
17150                initialization_options: Some(json!({
17151                    "some other init value": false
17152                })),
17153                enable_lsp_tasks: false,
17154                fetch: None,
17155            },
17156        );
17157    });
17158    cx.executor().run_until_parked();
17159    assert_eq!(
17160        server_restarts.load(atomic::Ordering::Acquire),
17161        0,
17162        "Should not restart LSP server on an unrelated LSP settings change"
17163    );
17164
17165    update_test_project_settings(cx, |project_settings| {
17166        project_settings.lsp.insert(
17167            language_server_name.into(),
17168            LspSettings {
17169                binary: None,
17170                settings: None,
17171                initialization_options: Some(json!({
17172                    "anotherInitValue": false
17173                })),
17174                enable_lsp_tasks: false,
17175                fetch: None,
17176            },
17177        );
17178    });
17179    cx.executor().run_until_parked();
17180    assert_eq!(
17181        server_restarts.load(atomic::Ordering::Acquire),
17182        1,
17183        "Should restart LSP server on a related LSP settings change"
17184    );
17185
17186    update_test_project_settings(cx, |project_settings| {
17187        project_settings.lsp.insert(
17188            language_server_name.into(),
17189            LspSettings {
17190                binary: None,
17191                settings: None,
17192                initialization_options: Some(json!({
17193                    "anotherInitValue": false
17194                })),
17195                enable_lsp_tasks: false,
17196                fetch: None,
17197            },
17198        );
17199    });
17200    cx.executor().run_until_parked();
17201    assert_eq!(
17202        server_restarts.load(atomic::Ordering::Acquire),
17203        1,
17204        "Should not restart LSP server on a related LSP settings change that is the same"
17205    );
17206
17207    update_test_project_settings(cx, |project_settings| {
17208        project_settings.lsp.insert(
17209            language_server_name.into(),
17210            LspSettings {
17211                binary: None,
17212                settings: None,
17213                initialization_options: None,
17214                enable_lsp_tasks: false,
17215                fetch: None,
17216            },
17217        );
17218    });
17219    cx.executor().run_until_parked();
17220    assert_eq!(
17221        server_restarts.load(atomic::Ordering::Acquire),
17222        2,
17223        "Should restart LSP server on another related LSP settings change"
17224    );
17225}
17226
17227#[gpui::test]
17228async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17229    init_test(cx, |_| {});
17230
17231    let mut cx = EditorLspTestContext::new_rust(
17232        lsp::ServerCapabilities {
17233            completion_provider: Some(lsp::CompletionOptions {
17234                trigger_characters: Some(vec![".".to_string()]),
17235                resolve_provider: Some(true),
17236                ..Default::default()
17237            }),
17238            ..Default::default()
17239        },
17240        cx,
17241    )
17242    .await;
17243
17244    cx.set_state("fn main() { let a = 2ˇ; }");
17245    cx.simulate_keystroke(".");
17246    let completion_item = lsp::CompletionItem {
17247        label: "some".into(),
17248        kind: Some(lsp::CompletionItemKind::SNIPPET),
17249        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17250        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17251            kind: lsp::MarkupKind::Markdown,
17252            value: "```rust\nSome(2)\n```".to_string(),
17253        })),
17254        deprecated: Some(false),
17255        sort_text: Some("fffffff2".to_string()),
17256        filter_text: Some("some".to_string()),
17257        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17258        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17259            range: lsp::Range {
17260                start: lsp::Position {
17261                    line: 0,
17262                    character: 22,
17263                },
17264                end: lsp::Position {
17265                    line: 0,
17266                    character: 22,
17267                },
17268            },
17269            new_text: "Some(2)".to_string(),
17270        })),
17271        additional_text_edits: Some(vec![lsp::TextEdit {
17272            range: lsp::Range {
17273                start: lsp::Position {
17274                    line: 0,
17275                    character: 20,
17276                },
17277                end: lsp::Position {
17278                    line: 0,
17279                    character: 22,
17280                },
17281            },
17282            new_text: "".to_string(),
17283        }]),
17284        ..Default::default()
17285    };
17286
17287    let closure_completion_item = completion_item.clone();
17288    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17289        let task_completion_item = closure_completion_item.clone();
17290        async move {
17291            Ok(Some(lsp::CompletionResponse::Array(vec![
17292                task_completion_item,
17293            ])))
17294        }
17295    });
17296
17297    request.next().await;
17298
17299    cx.condition(|editor, _| editor.context_menu_visible())
17300        .await;
17301    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17302        editor
17303            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17304            .unwrap()
17305    });
17306    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17307
17308    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17309        let task_completion_item = completion_item.clone();
17310        async move { Ok(task_completion_item) }
17311    })
17312    .next()
17313    .await
17314    .unwrap();
17315    apply_additional_edits.await.unwrap();
17316    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17317}
17318
17319#[gpui::test]
17320async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17321    init_test(cx, |_| {});
17322
17323    let mut cx = EditorLspTestContext::new_rust(
17324        lsp::ServerCapabilities {
17325            completion_provider: Some(lsp::CompletionOptions {
17326                trigger_characters: Some(vec![".".to_string()]),
17327                resolve_provider: Some(true),
17328                ..Default::default()
17329            }),
17330            ..Default::default()
17331        },
17332        cx,
17333    )
17334    .await;
17335
17336    cx.set_state("fn main() { let a = 2ˇ; }");
17337    cx.simulate_keystroke(".");
17338
17339    let item1 = lsp::CompletionItem {
17340        label: "method id()".to_string(),
17341        filter_text: Some("id".to_string()),
17342        detail: None,
17343        documentation: None,
17344        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17345            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17346            new_text: ".id".to_string(),
17347        })),
17348        ..lsp::CompletionItem::default()
17349    };
17350
17351    let item2 = lsp::CompletionItem {
17352        label: "other".to_string(),
17353        filter_text: Some("other".to_string()),
17354        detail: None,
17355        documentation: None,
17356        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17357            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17358            new_text: ".other".to_string(),
17359        })),
17360        ..lsp::CompletionItem::default()
17361    };
17362
17363    let item1 = item1.clone();
17364    cx.set_request_handler::<lsp::request::Completion, _, _>({
17365        let item1 = item1.clone();
17366        move |_, _, _| {
17367            let item1 = item1.clone();
17368            let item2 = item2.clone();
17369            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17370        }
17371    })
17372    .next()
17373    .await;
17374
17375    cx.condition(|editor, _| editor.context_menu_visible())
17376        .await;
17377    cx.update_editor(|editor, _, _| {
17378        let context_menu = editor.context_menu.borrow_mut();
17379        let context_menu = context_menu
17380            .as_ref()
17381            .expect("Should have the context menu deployed");
17382        match context_menu {
17383            CodeContextMenu::Completions(completions_menu) => {
17384                let completions = completions_menu.completions.borrow_mut();
17385                assert_eq!(
17386                    completions
17387                        .iter()
17388                        .map(|completion| &completion.label.text)
17389                        .collect::<Vec<_>>(),
17390                    vec!["method id()", "other"]
17391                )
17392            }
17393            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17394        }
17395    });
17396
17397    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17398        let item1 = item1.clone();
17399        move |_, item_to_resolve, _| {
17400            let item1 = item1.clone();
17401            async move {
17402                if item1 == item_to_resolve {
17403                    Ok(lsp::CompletionItem {
17404                        label: "method id()".to_string(),
17405                        filter_text: Some("id".to_string()),
17406                        detail: Some("Now resolved!".to_string()),
17407                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17408                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17409                            range: lsp::Range::new(
17410                                lsp::Position::new(0, 22),
17411                                lsp::Position::new(0, 22),
17412                            ),
17413                            new_text: ".id".to_string(),
17414                        })),
17415                        ..lsp::CompletionItem::default()
17416                    })
17417                } else {
17418                    Ok(item_to_resolve)
17419                }
17420            }
17421        }
17422    })
17423    .next()
17424    .await
17425    .unwrap();
17426    cx.run_until_parked();
17427
17428    cx.update_editor(|editor, window, cx| {
17429        editor.context_menu_next(&Default::default(), window, cx);
17430    });
17431
17432    cx.update_editor(|editor, _, _| {
17433        let context_menu = editor.context_menu.borrow_mut();
17434        let context_menu = context_menu
17435            .as_ref()
17436            .expect("Should have the context menu deployed");
17437        match context_menu {
17438            CodeContextMenu::Completions(completions_menu) => {
17439                let completions = completions_menu.completions.borrow_mut();
17440                assert_eq!(
17441                    completions
17442                        .iter()
17443                        .map(|completion| &completion.label.text)
17444                        .collect::<Vec<_>>(),
17445                    vec!["method id() Now resolved!", "other"],
17446                    "Should update first completion label, but not second as the filter text did not match."
17447                );
17448            }
17449            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17450        }
17451    });
17452}
17453
17454#[gpui::test]
17455async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17456    init_test(cx, |_| {});
17457    let mut cx = EditorLspTestContext::new_rust(
17458        lsp::ServerCapabilities {
17459            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17460            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17461            completion_provider: Some(lsp::CompletionOptions {
17462                resolve_provider: Some(true),
17463                ..Default::default()
17464            }),
17465            ..Default::default()
17466        },
17467        cx,
17468    )
17469    .await;
17470    cx.set_state(indoc! {"
17471        struct TestStruct {
17472            field: i32
17473        }
17474
17475        fn mainˇ() {
17476            let unused_var = 42;
17477            let test_struct = TestStruct { field: 42 };
17478        }
17479    "});
17480    let symbol_range = cx.lsp_range(indoc! {"
17481        struct TestStruct {
17482            field: i32
17483        }
17484
17485        «fn main»() {
17486            let unused_var = 42;
17487            let test_struct = TestStruct { field: 42 };
17488        }
17489    "});
17490    let mut hover_requests =
17491        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17492            Ok(Some(lsp::Hover {
17493                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17494                    kind: lsp::MarkupKind::Markdown,
17495                    value: "Function documentation".to_string(),
17496                }),
17497                range: Some(symbol_range),
17498            }))
17499        });
17500
17501    // Case 1: Test that code action menu hide hover popover
17502    cx.dispatch_action(Hover);
17503    hover_requests.next().await;
17504    cx.condition(|editor, _| editor.hover_state.visible()).await;
17505    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17506        move |_, _, _| async move {
17507            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17508                lsp::CodeAction {
17509                    title: "Remove unused variable".to_string(),
17510                    kind: Some(CodeActionKind::QUICKFIX),
17511                    edit: Some(lsp::WorkspaceEdit {
17512                        changes: Some(
17513                            [(
17514                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17515                                vec![lsp::TextEdit {
17516                                    range: lsp::Range::new(
17517                                        lsp::Position::new(5, 4),
17518                                        lsp::Position::new(5, 27),
17519                                    ),
17520                                    new_text: "".to_string(),
17521                                }],
17522                            )]
17523                            .into_iter()
17524                            .collect(),
17525                        ),
17526                        ..Default::default()
17527                    }),
17528                    ..Default::default()
17529                },
17530            )]))
17531        },
17532    );
17533    cx.update_editor(|editor, window, cx| {
17534        editor.toggle_code_actions(
17535            &ToggleCodeActions {
17536                deployed_from: None,
17537                quick_launch: false,
17538            },
17539            window,
17540            cx,
17541        );
17542    });
17543    code_action_requests.next().await;
17544    cx.run_until_parked();
17545    cx.condition(|editor, _| editor.context_menu_visible())
17546        .await;
17547    cx.update_editor(|editor, _, _| {
17548        assert!(
17549            !editor.hover_state.visible(),
17550            "Hover popover should be hidden when code action menu is shown"
17551        );
17552        // Hide code actions
17553        editor.context_menu.take();
17554    });
17555
17556    // Case 2: Test that code completions hide hover popover
17557    cx.dispatch_action(Hover);
17558    hover_requests.next().await;
17559    cx.condition(|editor, _| editor.hover_state.visible()).await;
17560    let counter = Arc::new(AtomicUsize::new(0));
17561    let mut completion_requests =
17562        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17563            let counter = counter.clone();
17564            async move {
17565                counter.fetch_add(1, atomic::Ordering::Release);
17566                Ok(Some(lsp::CompletionResponse::Array(vec![
17567                    lsp::CompletionItem {
17568                        label: "main".into(),
17569                        kind: Some(lsp::CompletionItemKind::FUNCTION),
17570                        detail: Some("() -> ()".to_string()),
17571                        ..Default::default()
17572                    },
17573                    lsp::CompletionItem {
17574                        label: "TestStruct".into(),
17575                        kind: Some(lsp::CompletionItemKind::STRUCT),
17576                        detail: Some("struct TestStruct".to_string()),
17577                        ..Default::default()
17578                    },
17579                ])))
17580            }
17581        });
17582    cx.update_editor(|editor, window, cx| {
17583        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
17584    });
17585    completion_requests.next().await;
17586    cx.condition(|editor, _| editor.context_menu_visible())
17587        .await;
17588    cx.update_editor(|editor, _, _| {
17589        assert!(
17590            !editor.hover_state.visible(),
17591            "Hover popover should be hidden when completion menu is shown"
17592        );
17593    });
17594}
17595
17596#[gpui::test]
17597async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
17598    init_test(cx, |_| {});
17599
17600    let mut cx = EditorLspTestContext::new_rust(
17601        lsp::ServerCapabilities {
17602            completion_provider: Some(lsp::CompletionOptions {
17603                trigger_characters: Some(vec![".".to_string()]),
17604                resolve_provider: Some(true),
17605                ..Default::default()
17606            }),
17607            ..Default::default()
17608        },
17609        cx,
17610    )
17611    .await;
17612
17613    cx.set_state("fn main() { let a = 2ˇ; }");
17614    cx.simulate_keystroke(".");
17615
17616    let unresolved_item_1 = lsp::CompletionItem {
17617        label: "id".to_string(),
17618        filter_text: Some("id".to_string()),
17619        detail: None,
17620        documentation: None,
17621        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17622            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17623            new_text: ".id".to_string(),
17624        })),
17625        ..lsp::CompletionItem::default()
17626    };
17627    let resolved_item_1 = lsp::CompletionItem {
17628        additional_text_edits: Some(vec![lsp::TextEdit {
17629            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17630            new_text: "!!".to_string(),
17631        }]),
17632        ..unresolved_item_1.clone()
17633    };
17634    let unresolved_item_2 = lsp::CompletionItem {
17635        label: "other".to_string(),
17636        filter_text: Some("other".to_string()),
17637        detail: None,
17638        documentation: None,
17639        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17640            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17641            new_text: ".other".to_string(),
17642        })),
17643        ..lsp::CompletionItem::default()
17644    };
17645    let resolved_item_2 = lsp::CompletionItem {
17646        additional_text_edits: Some(vec![lsp::TextEdit {
17647            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17648            new_text: "??".to_string(),
17649        }]),
17650        ..unresolved_item_2.clone()
17651    };
17652
17653    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
17654    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
17655    cx.lsp
17656        .server
17657        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17658            let unresolved_item_1 = unresolved_item_1.clone();
17659            let resolved_item_1 = resolved_item_1.clone();
17660            let unresolved_item_2 = unresolved_item_2.clone();
17661            let resolved_item_2 = resolved_item_2.clone();
17662            let resolve_requests_1 = resolve_requests_1.clone();
17663            let resolve_requests_2 = resolve_requests_2.clone();
17664            move |unresolved_request, _| {
17665                let unresolved_item_1 = unresolved_item_1.clone();
17666                let resolved_item_1 = resolved_item_1.clone();
17667                let unresolved_item_2 = unresolved_item_2.clone();
17668                let resolved_item_2 = resolved_item_2.clone();
17669                let resolve_requests_1 = resolve_requests_1.clone();
17670                let resolve_requests_2 = resolve_requests_2.clone();
17671                async move {
17672                    if unresolved_request == unresolved_item_1 {
17673                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
17674                        Ok(resolved_item_1.clone())
17675                    } else if unresolved_request == unresolved_item_2 {
17676                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
17677                        Ok(resolved_item_2.clone())
17678                    } else {
17679                        panic!("Unexpected completion item {unresolved_request:?}")
17680                    }
17681                }
17682            }
17683        })
17684        .detach();
17685
17686    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17687        let unresolved_item_1 = unresolved_item_1.clone();
17688        let unresolved_item_2 = unresolved_item_2.clone();
17689        async move {
17690            Ok(Some(lsp::CompletionResponse::Array(vec![
17691                unresolved_item_1,
17692                unresolved_item_2,
17693            ])))
17694        }
17695    })
17696    .next()
17697    .await;
17698
17699    cx.condition(|editor, _| editor.context_menu_visible())
17700        .await;
17701    cx.update_editor(|editor, _, _| {
17702        let context_menu = editor.context_menu.borrow_mut();
17703        let context_menu = context_menu
17704            .as_ref()
17705            .expect("Should have the context menu deployed");
17706        match context_menu {
17707            CodeContextMenu::Completions(completions_menu) => {
17708                let completions = completions_menu.completions.borrow_mut();
17709                assert_eq!(
17710                    completions
17711                        .iter()
17712                        .map(|completion| &completion.label.text)
17713                        .collect::<Vec<_>>(),
17714                    vec!["id", "other"]
17715                )
17716            }
17717            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17718        }
17719    });
17720    cx.run_until_parked();
17721
17722    cx.update_editor(|editor, window, cx| {
17723        editor.context_menu_next(&ContextMenuNext, window, cx);
17724    });
17725    cx.run_until_parked();
17726    cx.update_editor(|editor, window, cx| {
17727        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17728    });
17729    cx.run_until_parked();
17730    cx.update_editor(|editor, window, cx| {
17731        editor.context_menu_next(&ContextMenuNext, window, cx);
17732    });
17733    cx.run_until_parked();
17734    cx.update_editor(|editor, window, cx| {
17735        editor
17736            .compose_completion(&ComposeCompletion::default(), window, cx)
17737            .expect("No task returned")
17738    })
17739    .await
17740    .expect("Completion failed");
17741    cx.run_until_parked();
17742
17743    cx.update_editor(|editor, _, cx| {
17744        assert_eq!(
17745            resolve_requests_1.load(atomic::Ordering::Acquire),
17746            1,
17747            "Should always resolve once despite multiple selections"
17748        );
17749        assert_eq!(
17750            resolve_requests_2.load(atomic::Ordering::Acquire),
17751            1,
17752            "Should always resolve once after multiple selections and applying the completion"
17753        );
17754        assert_eq!(
17755            editor.text(cx),
17756            "fn main() { let a = ??.other; }",
17757            "Should use resolved data when applying the completion"
17758        );
17759    });
17760}
17761
17762#[gpui::test]
17763async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
17764    init_test(cx, |_| {});
17765
17766    let item_0 = lsp::CompletionItem {
17767        label: "abs".into(),
17768        insert_text: Some("abs".into()),
17769        data: Some(json!({ "very": "special"})),
17770        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
17771        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
17772            lsp::InsertReplaceEdit {
17773                new_text: "abs".to_string(),
17774                insert: lsp::Range::default(),
17775                replace: lsp::Range::default(),
17776            },
17777        )),
17778        ..lsp::CompletionItem::default()
17779    };
17780    let items = iter::once(item_0.clone())
17781        .chain((11..51).map(|i| lsp::CompletionItem {
17782            label: format!("item_{}", i),
17783            insert_text: Some(format!("item_{}", i)),
17784            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
17785            ..lsp::CompletionItem::default()
17786        }))
17787        .collect::<Vec<_>>();
17788
17789    let default_commit_characters = vec!["?".to_string()];
17790    let default_data = json!({ "default": "data"});
17791    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
17792    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
17793    let default_edit_range = lsp::Range {
17794        start: lsp::Position {
17795            line: 0,
17796            character: 5,
17797        },
17798        end: lsp::Position {
17799            line: 0,
17800            character: 5,
17801        },
17802    };
17803
17804    let mut cx = EditorLspTestContext::new_rust(
17805        lsp::ServerCapabilities {
17806            completion_provider: Some(lsp::CompletionOptions {
17807                trigger_characters: Some(vec![".".to_string()]),
17808                resolve_provider: Some(true),
17809                ..Default::default()
17810            }),
17811            ..Default::default()
17812        },
17813        cx,
17814    )
17815    .await;
17816
17817    cx.set_state("fn main() { let a = 2ˇ; }");
17818    cx.simulate_keystroke(".");
17819
17820    let completion_data = default_data.clone();
17821    let completion_characters = default_commit_characters.clone();
17822    let completion_items = items.clone();
17823    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17824        let default_data = completion_data.clone();
17825        let default_commit_characters = completion_characters.clone();
17826        let items = completion_items.clone();
17827        async move {
17828            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
17829                items,
17830                item_defaults: Some(lsp::CompletionListItemDefaults {
17831                    data: Some(default_data.clone()),
17832                    commit_characters: Some(default_commit_characters.clone()),
17833                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
17834                        default_edit_range,
17835                    )),
17836                    insert_text_format: Some(default_insert_text_format),
17837                    insert_text_mode: Some(default_insert_text_mode),
17838                }),
17839                ..lsp::CompletionList::default()
17840            })))
17841        }
17842    })
17843    .next()
17844    .await;
17845
17846    let resolved_items = Arc::new(Mutex::new(Vec::new()));
17847    cx.lsp
17848        .server
17849        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17850            let closure_resolved_items = resolved_items.clone();
17851            move |item_to_resolve, _| {
17852                let closure_resolved_items = closure_resolved_items.clone();
17853                async move {
17854                    closure_resolved_items.lock().push(item_to_resolve.clone());
17855                    Ok(item_to_resolve)
17856                }
17857            }
17858        })
17859        .detach();
17860
17861    cx.condition(|editor, _| editor.context_menu_visible())
17862        .await;
17863    cx.run_until_parked();
17864    cx.update_editor(|editor, _, _| {
17865        let menu = editor.context_menu.borrow_mut();
17866        match menu.as_ref().expect("should have the completions menu") {
17867            CodeContextMenu::Completions(completions_menu) => {
17868                assert_eq!(
17869                    completions_menu
17870                        .entries
17871                        .borrow()
17872                        .iter()
17873                        .map(|mat| mat.string.clone())
17874                        .collect::<Vec<String>>(),
17875                    items
17876                        .iter()
17877                        .map(|completion| completion.label.clone())
17878                        .collect::<Vec<String>>()
17879                );
17880            }
17881            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
17882        }
17883    });
17884    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
17885    // with 4 from the end.
17886    assert_eq!(
17887        *resolved_items.lock(),
17888        [&items[0..16], &items[items.len() - 4..items.len()]]
17889            .concat()
17890            .iter()
17891            .cloned()
17892            .map(|mut item| {
17893                if item.data.is_none() {
17894                    item.data = Some(default_data.clone());
17895                }
17896                item
17897            })
17898            .collect::<Vec<lsp::CompletionItem>>(),
17899        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
17900    );
17901    resolved_items.lock().clear();
17902
17903    cx.update_editor(|editor, window, cx| {
17904        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17905    });
17906    cx.run_until_parked();
17907    // Completions that have already been resolved are skipped.
17908    assert_eq!(
17909        *resolved_items.lock(),
17910        items[items.len() - 17..items.len() - 4]
17911            .iter()
17912            .cloned()
17913            .map(|mut item| {
17914                if item.data.is_none() {
17915                    item.data = Some(default_data.clone());
17916                }
17917                item
17918            })
17919            .collect::<Vec<lsp::CompletionItem>>()
17920    );
17921    resolved_items.lock().clear();
17922}
17923
17924#[gpui::test]
17925async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
17926    init_test(cx, |_| {});
17927
17928    let mut cx = EditorLspTestContext::new(
17929        Language::new(
17930            LanguageConfig {
17931                matcher: LanguageMatcher {
17932                    path_suffixes: vec!["jsx".into()],
17933                    ..Default::default()
17934                },
17935                overrides: [(
17936                    "element".into(),
17937                    LanguageConfigOverride {
17938                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
17939                        ..Default::default()
17940                    },
17941                )]
17942                .into_iter()
17943                .collect(),
17944                ..Default::default()
17945            },
17946            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
17947        )
17948        .with_override_query("(jsx_self_closing_element) @element")
17949        .unwrap(),
17950        lsp::ServerCapabilities {
17951            completion_provider: Some(lsp::CompletionOptions {
17952                trigger_characters: Some(vec![":".to_string()]),
17953                ..Default::default()
17954            }),
17955            ..Default::default()
17956        },
17957        cx,
17958    )
17959    .await;
17960
17961    cx.lsp
17962        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
17963            Ok(Some(lsp::CompletionResponse::Array(vec![
17964                lsp::CompletionItem {
17965                    label: "bg-blue".into(),
17966                    ..Default::default()
17967                },
17968                lsp::CompletionItem {
17969                    label: "bg-red".into(),
17970                    ..Default::default()
17971                },
17972                lsp::CompletionItem {
17973                    label: "bg-yellow".into(),
17974                    ..Default::default()
17975                },
17976            ])))
17977        });
17978
17979    cx.set_state(r#"<p class="bgˇ" />"#);
17980
17981    // Trigger completion when typing a dash, because the dash is an extra
17982    // word character in the 'element' scope, which contains the cursor.
17983    cx.simulate_keystroke("-");
17984    cx.executor().run_until_parked();
17985    cx.update_editor(|editor, _, _| {
17986        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17987        {
17988            assert_eq!(
17989                completion_menu_entries(menu),
17990                &["bg-blue", "bg-red", "bg-yellow"]
17991            );
17992        } else {
17993            panic!("expected completion menu to be open");
17994        }
17995    });
17996
17997    cx.simulate_keystroke("l");
17998    cx.executor().run_until_parked();
17999    cx.update_editor(|editor, _, _| {
18000        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18001        {
18002            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
18003        } else {
18004            panic!("expected completion menu to be open");
18005        }
18006    });
18007
18008    // When filtering completions, consider the character after the '-' to
18009    // be the start of a subword.
18010    cx.set_state(r#"<p class="yelˇ" />"#);
18011    cx.simulate_keystroke("l");
18012    cx.executor().run_until_parked();
18013    cx.update_editor(|editor, _, _| {
18014        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18015        {
18016            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
18017        } else {
18018            panic!("expected completion menu to be open");
18019        }
18020    });
18021}
18022
18023fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
18024    let entries = menu.entries.borrow();
18025    entries.iter().map(|mat| mat.string.clone()).collect()
18026}
18027
18028#[gpui::test]
18029async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
18030    init_test(cx, |settings| {
18031        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
18032            Formatter::Prettier,
18033        )))
18034    });
18035
18036    let fs = FakeFs::new(cx.executor());
18037    fs.insert_file(path!("/file.ts"), Default::default()).await;
18038
18039    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
18040    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18041
18042    language_registry.add(Arc::new(Language::new(
18043        LanguageConfig {
18044            name: "TypeScript".into(),
18045            matcher: LanguageMatcher {
18046                path_suffixes: vec!["ts".to_string()],
18047                ..Default::default()
18048            },
18049            ..Default::default()
18050        },
18051        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18052    )));
18053    update_test_language_settings(cx, |settings| {
18054        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
18055    });
18056
18057    let test_plugin = "test_plugin";
18058    let _ = language_registry.register_fake_lsp(
18059        "TypeScript",
18060        FakeLspAdapter {
18061            prettier_plugins: vec![test_plugin],
18062            ..Default::default()
18063        },
18064    );
18065
18066    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
18067    let buffer = project
18068        .update(cx, |project, cx| {
18069            project.open_local_buffer(path!("/file.ts"), cx)
18070        })
18071        .await
18072        .unwrap();
18073
18074    let buffer_text = "one\ntwo\nthree\n";
18075    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18076    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18077    editor.update_in(cx, |editor, window, cx| {
18078        editor.set_text(buffer_text, window, cx)
18079    });
18080
18081    editor
18082        .update_in(cx, |editor, window, cx| {
18083            editor.perform_format(
18084                project.clone(),
18085                FormatTrigger::Manual,
18086                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18087                window,
18088                cx,
18089            )
18090        })
18091        .unwrap()
18092        .await;
18093    assert_eq!(
18094        editor.update(cx, |editor, cx| editor.text(cx)),
18095        buffer_text.to_string() + prettier_format_suffix,
18096        "Test prettier formatting was not applied to the original buffer text",
18097    );
18098
18099    update_test_language_settings(cx, |settings| {
18100        settings.defaults.formatter = Some(SelectedFormatter::Auto)
18101    });
18102    let format = editor.update_in(cx, |editor, window, cx| {
18103        editor.perform_format(
18104            project.clone(),
18105            FormatTrigger::Manual,
18106            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18107            window,
18108            cx,
18109        )
18110    });
18111    format.await.unwrap();
18112    assert_eq!(
18113        editor.update(cx, |editor, cx| editor.text(cx)),
18114        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18115        "Autoformatting (via test prettier) was not applied to the original buffer text",
18116    );
18117}
18118
18119#[gpui::test]
18120async fn test_addition_reverts(cx: &mut TestAppContext) {
18121    init_test(cx, |_| {});
18122    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18123    let base_text = indoc! {r#"
18124        struct Row;
18125        struct Row1;
18126        struct Row2;
18127
18128        struct Row4;
18129        struct Row5;
18130        struct Row6;
18131
18132        struct Row8;
18133        struct Row9;
18134        struct Row10;"#};
18135
18136    // When addition hunks are not adjacent to carets, no hunk revert is performed
18137    assert_hunk_revert(
18138        indoc! {r#"struct Row;
18139                   struct Row1;
18140                   struct Row1.1;
18141                   struct Row1.2;
18142                   struct Row2;ˇ
18143
18144                   struct Row4;
18145                   struct Row5;
18146                   struct Row6;
18147
18148                   struct Row8;
18149                   ˇstruct Row9;
18150                   struct Row9.1;
18151                   struct Row9.2;
18152                   struct Row9.3;
18153                   struct Row10;"#},
18154        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18155        indoc! {r#"struct Row;
18156                   struct Row1;
18157                   struct Row1.1;
18158                   struct Row1.2;
18159                   struct Row2;ˇ
18160
18161                   struct Row4;
18162                   struct Row5;
18163                   struct Row6;
18164
18165                   struct Row8;
18166                   ˇstruct Row9;
18167                   struct Row9.1;
18168                   struct Row9.2;
18169                   struct Row9.3;
18170                   struct Row10;"#},
18171        base_text,
18172        &mut cx,
18173    );
18174    // Same for selections
18175    assert_hunk_revert(
18176        indoc! {r#"struct Row;
18177                   struct Row1;
18178                   struct Row2;
18179                   struct Row2.1;
18180                   struct Row2.2;
18181                   «ˇ
18182                   struct Row4;
18183                   struct» Row5;
18184                   «struct Row6;
18185                   ˇ»
18186                   struct Row9.1;
18187                   struct Row9.2;
18188                   struct Row9.3;
18189                   struct Row8;
18190                   struct Row9;
18191                   struct Row10;"#},
18192        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18193        indoc! {r#"struct Row;
18194                   struct Row1;
18195                   struct Row2;
18196                   struct Row2.1;
18197                   struct Row2.2;
18198                   «ˇ
18199                   struct Row4;
18200                   struct» Row5;
18201                   «struct Row6;
18202                   ˇ»
18203                   struct Row9.1;
18204                   struct Row9.2;
18205                   struct Row9.3;
18206                   struct Row8;
18207                   struct Row9;
18208                   struct Row10;"#},
18209        base_text,
18210        &mut cx,
18211    );
18212
18213    // When carets and selections intersect the addition hunks, those are reverted.
18214    // Adjacent carets got merged.
18215    assert_hunk_revert(
18216        indoc! {r#"struct Row;
18217                   ˇ// something on the top
18218                   struct Row1;
18219                   struct Row2;
18220                   struct Roˇw3.1;
18221                   struct Row2.2;
18222                   struct Row2.3;ˇ
18223
18224                   struct Row4;
18225                   struct ˇRow5.1;
18226                   struct Row5.2;
18227                   struct «Rowˇ»5.3;
18228                   struct Row5;
18229                   struct Row6;
18230                   ˇ
18231                   struct Row9.1;
18232                   struct «Rowˇ»9.2;
18233                   struct «ˇRow»9.3;
18234                   struct Row8;
18235                   struct Row9;
18236                   «ˇ// something on bottom»
18237                   struct Row10;"#},
18238        vec![
18239            DiffHunkStatusKind::Added,
18240            DiffHunkStatusKind::Added,
18241            DiffHunkStatusKind::Added,
18242            DiffHunkStatusKind::Added,
18243            DiffHunkStatusKind::Added,
18244        ],
18245        indoc! {r#"struct Row;
18246                   ˇstruct Row1;
18247                   struct Row2;
18248                   ˇ
18249                   struct Row4;
18250                   ˇstruct Row5;
18251                   struct Row6;
18252                   ˇ
18253                   ˇstruct Row8;
18254                   struct Row9;
18255                   ˇstruct Row10;"#},
18256        base_text,
18257        &mut cx,
18258    );
18259}
18260
18261#[gpui::test]
18262async fn test_modification_reverts(cx: &mut TestAppContext) {
18263    init_test(cx, |_| {});
18264    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18265    let base_text = indoc! {r#"
18266        struct Row;
18267        struct Row1;
18268        struct Row2;
18269
18270        struct Row4;
18271        struct Row5;
18272        struct Row6;
18273
18274        struct Row8;
18275        struct Row9;
18276        struct Row10;"#};
18277
18278    // Modification hunks behave the same as the addition ones.
18279    assert_hunk_revert(
18280        indoc! {r#"struct Row;
18281                   struct Row1;
18282                   struct Row33;
18283                   ˇ
18284                   struct Row4;
18285                   struct Row5;
18286                   struct Row6;
18287                   ˇ
18288                   struct Row99;
18289                   struct Row9;
18290                   struct Row10;"#},
18291        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18292        indoc! {r#"struct Row;
18293                   struct Row1;
18294                   struct Row33;
18295                   ˇ
18296                   struct Row4;
18297                   struct Row5;
18298                   struct Row6;
18299                   ˇ
18300                   struct Row99;
18301                   struct Row9;
18302                   struct Row10;"#},
18303        base_text,
18304        &mut cx,
18305    );
18306    assert_hunk_revert(
18307        indoc! {r#"struct Row;
18308                   struct Row1;
18309                   struct Row33;
18310                   «ˇ
18311                   struct Row4;
18312                   struct» Row5;
18313                   «struct Row6;
18314                   ˇ»
18315                   struct Row99;
18316                   struct Row9;
18317                   struct Row10;"#},
18318        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18319        indoc! {r#"struct Row;
18320                   struct Row1;
18321                   struct Row33;
18322                   «ˇ
18323                   struct Row4;
18324                   struct» Row5;
18325                   «struct Row6;
18326                   ˇ»
18327                   struct Row99;
18328                   struct Row9;
18329                   struct Row10;"#},
18330        base_text,
18331        &mut cx,
18332    );
18333
18334    assert_hunk_revert(
18335        indoc! {r#"ˇstruct Row1.1;
18336                   struct Row1;
18337                   «ˇstr»uct Row22;
18338
18339                   struct ˇRow44;
18340                   struct Row5;
18341                   struct «Rˇ»ow66;ˇ
18342
18343                   «struˇ»ct Row88;
18344                   struct Row9;
18345                   struct Row1011;ˇ"#},
18346        vec![
18347            DiffHunkStatusKind::Modified,
18348            DiffHunkStatusKind::Modified,
18349            DiffHunkStatusKind::Modified,
18350            DiffHunkStatusKind::Modified,
18351            DiffHunkStatusKind::Modified,
18352            DiffHunkStatusKind::Modified,
18353        ],
18354        indoc! {r#"struct Row;
18355                   ˇstruct Row1;
18356                   struct Row2;
18357                   ˇ
18358                   struct Row4;
18359                   ˇstruct Row5;
18360                   struct Row6;
18361                   ˇ
18362                   struct Row8;
18363                   ˇstruct Row9;
18364                   struct Row10;ˇ"#},
18365        base_text,
18366        &mut cx,
18367    );
18368}
18369
18370#[gpui::test]
18371async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18372    init_test(cx, |_| {});
18373    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18374    let base_text = indoc! {r#"
18375        one
18376
18377        two
18378        three
18379        "#};
18380
18381    cx.set_head_text(base_text);
18382    cx.set_state("\nˇ\n");
18383    cx.executor().run_until_parked();
18384    cx.update_editor(|editor, _window, cx| {
18385        editor.expand_selected_diff_hunks(cx);
18386    });
18387    cx.executor().run_until_parked();
18388    cx.update_editor(|editor, window, cx| {
18389        editor.backspace(&Default::default(), window, cx);
18390    });
18391    cx.run_until_parked();
18392    cx.assert_state_with_diff(
18393        indoc! {r#"
18394
18395        - two
18396        - threeˇ
18397        +
18398        "#}
18399        .to_string(),
18400    );
18401}
18402
18403#[gpui::test]
18404async fn test_deletion_reverts(cx: &mut TestAppContext) {
18405    init_test(cx, |_| {});
18406    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18407    let base_text = indoc! {r#"struct Row;
18408struct Row1;
18409struct Row2;
18410
18411struct Row4;
18412struct Row5;
18413struct Row6;
18414
18415struct Row8;
18416struct Row9;
18417struct Row10;"#};
18418
18419    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18420    assert_hunk_revert(
18421        indoc! {r#"struct Row;
18422                   struct Row2;
18423
18424                   ˇstruct Row4;
18425                   struct Row5;
18426                   struct Row6;
18427                   ˇ
18428                   struct Row8;
18429                   struct Row10;"#},
18430        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18431        indoc! {r#"struct Row;
18432                   struct Row2;
18433
18434                   ˇstruct Row4;
18435                   struct Row5;
18436                   struct Row6;
18437                   ˇ
18438                   struct Row8;
18439                   struct Row10;"#},
18440        base_text,
18441        &mut cx,
18442    );
18443    assert_hunk_revert(
18444        indoc! {r#"struct Row;
18445                   struct Row2;
18446
18447                   «ˇstruct Row4;
18448                   struct» Row5;
18449                   «struct Row6;
18450                   ˇ»
18451                   struct Row8;
18452                   struct Row10;"#},
18453        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18454        indoc! {r#"struct Row;
18455                   struct Row2;
18456
18457                   «ˇstruct Row4;
18458                   struct» Row5;
18459                   «struct Row6;
18460                   ˇ»
18461                   struct Row8;
18462                   struct Row10;"#},
18463        base_text,
18464        &mut cx,
18465    );
18466
18467    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18468    assert_hunk_revert(
18469        indoc! {r#"struct Row;
18470                   ˇstruct Row2;
18471
18472                   struct Row4;
18473                   struct Row5;
18474                   struct Row6;
18475
18476                   struct Row8;ˇ
18477                   struct Row10;"#},
18478        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18479        indoc! {r#"struct Row;
18480                   struct Row1;
18481                   ˇstruct Row2;
18482
18483                   struct Row4;
18484                   struct Row5;
18485                   struct Row6;
18486
18487                   struct Row8;ˇ
18488                   struct Row9;
18489                   struct Row10;"#},
18490        base_text,
18491        &mut cx,
18492    );
18493    assert_hunk_revert(
18494        indoc! {r#"struct Row;
18495                   struct Row2«ˇ;
18496                   struct Row4;
18497                   struct» Row5;
18498                   «struct Row6;
18499
18500                   struct Row8;ˇ»
18501                   struct Row10;"#},
18502        vec![
18503            DiffHunkStatusKind::Deleted,
18504            DiffHunkStatusKind::Deleted,
18505            DiffHunkStatusKind::Deleted,
18506        ],
18507        indoc! {r#"struct Row;
18508                   struct Row1;
18509                   struct Row2«ˇ;
18510
18511                   struct Row4;
18512                   struct» Row5;
18513                   «struct Row6;
18514
18515                   struct Row8;ˇ»
18516                   struct Row9;
18517                   struct Row10;"#},
18518        base_text,
18519        &mut cx,
18520    );
18521}
18522
18523#[gpui::test]
18524async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18525    init_test(cx, |_| {});
18526
18527    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18528    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18529    let base_text_3 =
18530        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18531
18532    let text_1 = edit_first_char_of_every_line(base_text_1);
18533    let text_2 = edit_first_char_of_every_line(base_text_2);
18534    let text_3 = edit_first_char_of_every_line(base_text_3);
18535
18536    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18537    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18538    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18539
18540    let multibuffer = cx.new(|cx| {
18541        let mut multibuffer = MultiBuffer::new(ReadWrite);
18542        multibuffer.push_excerpts(
18543            buffer_1.clone(),
18544            [
18545                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18546                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18547                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18548            ],
18549            cx,
18550        );
18551        multibuffer.push_excerpts(
18552            buffer_2.clone(),
18553            [
18554                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18555                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18556                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18557            ],
18558            cx,
18559        );
18560        multibuffer.push_excerpts(
18561            buffer_3.clone(),
18562            [
18563                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18564                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18565                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18566            ],
18567            cx,
18568        );
18569        multibuffer
18570    });
18571
18572    let fs = FakeFs::new(cx.executor());
18573    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
18574    let (editor, cx) = cx
18575        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
18576    editor.update_in(cx, |editor, _window, cx| {
18577        for (buffer, diff_base) in [
18578            (buffer_1.clone(), base_text_1),
18579            (buffer_2.clone(), base_text_2),
18580            (buffer_3.clone(), base_text_3),
18581        ] {
18582            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18583            editor
18584                .buffer
18585                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18586        }
18587    });
18588    cx.executor().run_until_parked();
18589
18590    editor.update_in(cx, |editor, window, cx| {
18591        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}");
18592        editor.select_all(&SelectAll, window, cx);
18593        editor.git_restore(&Default::default(), window, cx);
18594    });
18595    cx.executor().run_until_parked();
18596
18597    // When all ranges are selected, all buffer hunks are reverted.
18598    editor.update(cx, |editor, cx| {
18599        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");
18600    });
18601    buffer_1.update(cx, |buffer, _| {
18602        assert_eq!(buffer.text(), base_text_1);
18603    });
18604    buffer_2.update(cx, |buffer, _| {
18605        assert_eq!(buffer.text(), base_text_2);
18606    });
18607    buffer_3.update(cx, |buffer, _| {
18608        assert_eq!(buffer.text(), base_text_3);
18609    });
18610
18611    editor.update_in(cx, |editor, window, cx| {
18612        editor.undo(&Default::default(), window, cx);
18613    });
18614
18615    editor.update_in(cx, |editor, window, cx| {
18616        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18617            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
18618        });
18619        editor.git_restore(&Default::default(), window, cx);
18620    });
18621
18622    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
18623    // but not affect buffer_2 and its related excerpts.
18624    editor.update(cx, |editor, cx| {
18625        assert_eq!(
18626            editor.text(cx),
18627            "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}"
18628        );
18629    });
18630    buffer_1.update(cx, |buffer, _| {
18631        assert_eq!(buffer.text(), base_text_1);
18632    });
18633    buffer_2.update(cx, |buffer, _| {
18634        assert_eq!(
18635            buffer.text(),
18636            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
18637        );
18638    });
18639    buffer_3.update(cx, |buffer, _| {
18640        assert_eq!(
18641            buffer.text(),
18642            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
18643        );
18644    });
18645
18646    fn edit_first_char_of_every_line(text: &str) -> String {
18647        text.split('\n')
18648            .map(|line| format!("X{}", &line[1..]))
18649            .collect::<Vec<_>>()
18650            .join("\n")
18651    }
18652}
18653
18654#[gpui::test]
18655async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
18656    init_test(cx, |_| {});
18657
18658    let cols = 4;
18659    let rows = 10;
18660    let sample_text_1 = sample_text(rows, cols, 'a');
18661    assert_eq!(
18662        sample_text_1,
18663        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
18664    );
18665    let sample_text_2 = sample_text(rows, cols, 'l');
18666    assert_eq!(
18667        sample_text_2,
18668        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
18669    );
18670    let sample_text_3 = sample_text(rows, cols, 'v');
18671    assert_eq!(
18672        sample_text_3,
18673        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
18674    );
18675
18676    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
18677    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
18678    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
18679
18680    let multi_buffer = cx.new(|cx| {
18681        let mut multibuffer = MultiBuffer::new(ReadWrite);
18682        multibuffer.push_excerpts(
18683            buffer_1.clone(),
18684            [
18685                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18686                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18687                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18688            ],
18689            cx,
18690        );
18691        multibuffer.push_excerpts(
18692            buffer_2.clone(),
18693            [
18694                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18695                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18696                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18697            ],
18698            cx,
18699        );
18700        multibuffer.push_excerpts(
18701            buffer_3.clone(),
18702            [
18703                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18704                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18705                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18706            ],
18707            cx,
18708        );
18709        multibuffer
18710    });
18711
18712    let fs = FakeFs::new(cx.executor());
18713    fs.insert_tree(
18714        "/a",
18715        json!({
18716            "main.rs": sample_text_1,
18717            "other.rs": sample_text_2,
18718            "lib.rs": sample_text_3,
18719        }),
18720    )
18721    .await;
18722    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18723    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18724    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18725    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18726        Editor::new(
18727            EditorMode::full(),
18728            multi_buffer,
18729            Some(project.clone()),
18730            window,
18731            cx,
18732        )
18733    });
18734    let multibuffer_item_id = workspace
18735        .update(cx, |workspace, window, cx| {
18736            assert!(
18737                workspace.active_item(cx).is_none(),
18738                "active item should be None before the first item is added"
18739            );
18740            workspace.add_item_to_active_pane(
18741                Box::new(multi_buffer_editor.clone()),
18742                None,
18743                true,
18744                window,
18745                cx,
18746            );
18747            let active_item = workspace
18748                .active_item(cx)
18749                .expect("should have an active item after adding the multi buffer");
18750            assert!(
18751                !active_item.is_singleton(cx),
18752                "A multi buffer was expected to active after adding"
18753            );
18754            active_item.item_id()
18755        })
18756        .unwrap();
18757    cx.executor().run_until_parked();
18758
18759    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18760        editor.change_selections(
18761            SelectionEffects::scroll(Autoscroll::Next),
18762            window,
18763            cx,
18764            |s| s.select_ranges(Some(1..2)),
18765        );
18766        editor.open_excerpts(&OpenExcerpts, window, cx);
18767    });
18768    cx.executor().run_until_parked();
18769    let first_item_id = workspace
18770        .update(cx, |workspace, window, cx| {
18771            let active_item = workspace
18772                .active_item(cx)
18773                .expect("should have an active item after navigating into the 1st buffer");
18774            let first_item_id = active_item.item_id();
18775            assert_ne!(
18776                first_item_id, multibuffer_item_id,
18777                "Should navigate into the 1st buffer and activate it"
18778            );
18779            assert!(
18780                active_item.is_singleton(cx),
18781                "New active item should be a singleton buffer"
18782            );
18783            assert_eq!(
18784                active_item
18785                    .act_as::<Editor>(cx)
18786                    .expect("should have navigated into an editor for the 1st buffer")
18787                    .read(cx)
18788                    .text(cx),
18789                sample_text_1
18790            );
18791
18792            workspace
18793                .go_back(workspace.active_pane().downgrade(), window, cx)
18794                .detach_and_log_err(cx);
18795
18796            first_item_id
18797        })
18798        .unwrap();
18799    cx.executor().run_until_parked();
18800    workspace
18801        .update(cx, |workspace, _, cx| {
18802            let active_item = workspace
18803                .active_item(cx)
18804                .expect("should have an active item after navigating back");
18805            assert_eq!(
18806                active_item.item_id(),
18807                multibuffer_item_id,
18808                "Should navigate back to the multi buffer"
18809            );
18810            assert!(!active_item.is_singleton(cx));
18811        })
18812        .unwrap();
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(39..40)),
18820        );
18821        editor.open_excerpts(&OpenExcerpts, window, cx);
18822    });
18823    cx.executor().run_until_parked();
18824    let second_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 2nd buffer");
18829            let second_item_id = active_item.item_id();
18830            assert_ne!(
18831                second_item_id, multibuffer_item_id,
18832                "Should navigate away from the multibuffer"
18833            );
18834            assert_ne!(
18835                second_item_id, first_item_id,
18836                "Should navigate into the 2nd buffer and activate it"
18837            );
18838            assert!(
18839                active_item.is_singleton(cx),
18840                "New active item should be a singleton buffer"
18841            );
18842            assert_eq!(
18843                active_item
18844                    .act_as::<Editor>(cx)
18845                    .expect("should have navigated into an editor")
18846                    .read(cx)
18847                    .text(cx),
18848                sample_text_2
18849            );
18850
18851            workspace
18852                .go_back(workspace.active_pane().downgrade(), window, cx)
18853                .detach_and_log_err(cx);
18854
18855            second_item_id
18856        })
18857        .unwrap();
18858    cx.executor().run_until_parked();
18859    workspace
18860        .update(cx, |workspace, _, cx| {
18861            let active_item = workspace
18862                .active_item(cx)
18863                .expect("should have an active item after navigating back from the 2nd buffer");
18864            assert_eq!(
18865                active_item.item_id(),
18866                multibuffer_item_id,
18867                "Should navigate back from the 2nd buffer to the multi buffer"
18868            );
18869            assert!(!active_item.is_singleton(cx));
18870        })
18871        .unwrap();
18872
18873    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18874        editor.change_selections(
18875            SelectionEffects::scroll(Autoscroll::Next),
18876            window,
18877            cx,
18878            |s| s.select_ranges(Some(70..70)),
18879        );
18880        editor.open_excerpts(&OpenExcerpts, window, cx);
18881    });
18882    cx.executor().run_until_parked();
18883    workspace
18884        .update(cx, |workspace, window, cx| {
18885            let active_item = workspace
18886                .active_item(cx)
18887                .expect("should have an active item after navigating into the 3rd buffer");
18888            let third_item_id = active_item.item_id();
18889            assert_ne!(
18890                third_item_id, multibuffer_item_id,
18891                "Should navigate into the 3rd buffer and activate it"
18892            );
18893            assert_ne!(third_item_id, first_item_id);
18894            assert_ne!(third_item_id, second_item_id);
18895            assert!(
18896                active_item.is_singleton(cx),
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_3
18906            );
18907
18908            workspace
18909                .go_back(workspace.active_pane().downgrade(), window, cx)
18910                .detach_and_log_err(cx);
18911        })
18912        .unwrap();
18913    cx.executor().run_until_parked();
18914    workspace
18915        .update(cx, |workspace, _, cx| {
18916            let active_item = workspace
18917                .active_item(cx)
18918                .expect("should have an active item after navigating back from the 3rd buffer");
18919            assert_eq!(
18920                active_item.item_id(),
18921                multibuffer_item_id,
18922                "Should navigate back from the 3rd buffer to the multi buffer"
18923            );
18924            assert!(!active_item.is_singleton(cx));
18925        })
18926        .unwrap();
18927}
18928
18929#[gpui::test]
18930async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18931    init_test(cx, |_| {});
18932
18933    let mut cx = EditorTestContext::new(cx).await;
18934
18935    let diff_base = r#"
18936        use some::mod;
18937
18938        const A: u32 = 42;
18939
18940        fn main() {
18941            println!("hello");
18942
18943            println!("world");
18944        }
18945        "#
18946    .unindent();
18947
18948    cx.set_state(
18949        &r#"
18950        use some::modified;
18951
18952        ˇ
18953        fn main() {
18954            println!("hello there");
18955
18956            println!("around the");
18957            println!("world");
18958        }
18959        "#
18960        .unindent(),
18961    );
18962
18963    cx.set_head_text(&diff_base);
18964    executor.run_until_parked();
18965
18966    cx.update_editor(|editor, window, cx| {
18967        editor.go_to_next_hunk(&GoToHunk, window, cx);
18968        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18969    });
18970    executor.run_until_parked();
18971    cx.assert_state_with_diff(
18972        r#"
18973          use some::modified;
18974
18975
18976          fn main() {
18977        -     println!("hello");
18978        + ˇ    println!("hello there");
18979
18980              println!("around the");
18981              println!("world");
18982          }
18983        "#
18984        .unindent(),
18985    );
18986
18987    cx.update_editor(|editor, window, cx| {
18988        for _ in 0..2 {
18989            editor.go_to_next_hunk(&GoToHunk, window, cx);
18990            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18991        }
18992    });
18993    executor.run_until_parked();
18994    cx.assert_state_with_diff(
18995        r#"
18996        - use some::mod;
18997        + ˇuse some::modified;
18998
18999
19000          fn main() {
19001        -     println!("hello");
19002        +     println!("hello there");
19003
19004        +     println!("around the");
19005              println!("world");
19006          }
19007        "#
19008        .unindent(),
19009    );
19010
19011    cx.update_editor(|editor, window, cx| {
19012        editor.go_to_next_hunk(&GoToHunk, window, cx);
19013        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19014    });
19015    executor.run_until_parked();
19016    cx.assert_state_with_diff(
19017        r#"
19018        - use some::mod;
19019        + use some::modified;
19020
19021        - const A: u32 = 42;
19022          ˇ
19023          fn main() {
19024        -     println!("hello");
19025        +     println!("hello there");
19026
19027        +     println!("around the");
19028              println!("world");
19029          }
19030        "#
19031        .unindent(),
19032    );
19033
19034    cx.update_editor(|editor, window, cx| {
19035        editor.cancel(&Cancel, window, cx);
19036    });
19037
19038    cx.assert_state_with_diff(
19039        r#"
19040          use some::modified;
19041
19042          ˇ
19043          fn main() {
19044              println!("hello there");
19045
19046              println!("around the");
19047              println!("world");
19048          }
19049        "#
19050        .unindent(),
19051    );
19052}
19053
19054#[gpui::test]
19055async fn test_diff_base_change_with_expanded_diff_hunks(
19056    executor: BackgroundExecutor,
19057    cx: &mut TestAppContext,
19058) {
19059    init_test(cx, |_| {});
19060
19061    let mut cx = EditorTestContext::new(cx).await;
19062
19063    let diff_base = r#"
19064        use some::mod1;
19065        use some::mod2;
19066
19067        const A: u32 = 42;
19068        const B: u32 = 42;
19069        const C: u32 = 42;
19070
19071        fn main() {
19072            println!("hello");
19073
19074            println!("world");
19075        }
19076        "#
19077    .unindent();
19078
19079    cx.set_state(
19080        &r#"
19081        use some::mod2;
19082
19083        const A: u32 = 42;
19084        const C: u32 = 42;
19085
19086        fn main(ˇ) {
19087            //println!("hello");
19088
19089            println!("world");
19090            //
19091            //
19092        }
19093        "#
19094        .unindent(),
19095    );
19096
19097    cx.set_head_text(&diff_base);
19098    executor.run_until_parked();
19099
19100    cx.update_editor(|editor, window, cx| {
19101        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19102    });
19103    executor.run_until_parked();
19104    cx.assert_state_with_diff(
19105        r#"
19106        - use some::mod1;
19107          use some::mod2;
19108
19109          const A: u32 = 42;
19110        - const B: u32 = 42;
19111          const C: u32 = 42;
19112
19113          fn main(ˇ) {
19114        -     println!("hello");
19115        +     //println!("hello");
19116
19117              println!("world");
19118        +     //
19119        +     //
19120          }
19121        "#
19122        .unindent(),
19123    );
19124
19125    cx.set_head_text("new diff base!");
19126    executor.run_until_parked();
19127    cx.assert_state_with_diff(
19128        r#"
19129        - new diff base!
19130        + use some::mod2;
19131        +
19132        + const A: u32 = 42;
19133        + const C: u32 = 42;
19134        +
19135        + fn main(ˇ) {
19136        +     //println!("hello");
19137        +
19138        +     println!("world");
19139        +     //
19140        +     //
19141        + }
19142        "#
19143        .unindent(),
19144    );
19145}
19146
19147#[gpui::test]
19148async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19149    init_test(cx, |_| {});
19150
19151    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19152    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19153    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19154    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19155    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19156    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19157
19158    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19159    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19160    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19161
19162    let multi_buffer = cx.new(|cx| {
19163        let mut multibuffer = MultiBuffer::new(ReadWrite);
19164        multibuffer.push_excerpts(
19165            buffer_1.clone(),
19166            [
19167                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19168                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19169                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19170            ],
19171            cx,
19172        );
19173        multibuffer.push_excerpts(
19174            buffer_2.clone(),
19175            [
19176                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19177                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19178                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19179            ],
19180            cx,
19181        );
19182        multibuffer.push_excerpts(
19183            buffer_3.clone(),
19184            [
19185                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19186                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19187                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19188            ],
19189            cx,
19190        );
19191        multibuffer
19192    });
19193
19194    let editor =
19195        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19196    editor
19197        .update(cx, |editor, _window, cx| {
19198            for (buffer, diff_base) in [
19199                (buffer_1.clone(), file_1_old),
19200                (buffer_2.clone(), file_2_old),
19201                (buffer_3.clone(), file_3_old),
19202            ] {
19203                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19204                editor
19205                    .buffer
19206                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19207            }
19208        })
19209        .unwrap();
19210
19211    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19212    cx.run_until_parked();
19213
19214    cx.assert_editor_state(
19215        &"
19216            ˇaaa
19217            ccc
19218            ddd
19219
19220            ggg
19221            hhh
19222
19223
19224            lll
19225            mmm
19226            NNN
19227
19228            qqq
19229            rrr
19230
19231            uuu
19232            111
19233            222
19234            333
19235
19236            666
19237            777
19238
19239            000
19240            !!!"
19241        .unindent(),
19242    );
19243
19244    cx.update_editor(|editor, window, cx| {
19245        editor.select_all(&SelectAll, window, cx);
19246        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19247    });
19248    cx.executor().run_until_parked();
19249
19250    cx.assert_state_with_diff(
19251        "
19252            «aaa
19253          - bbb
19254            ccc
19255            ddd
19256
19257            ggg
19258            hhh
19259
19260
19261            lll
19262            mmm
19263          - nnn
19264          + NNN
19265
19266            qqq
19267            rrr
19268
19269            uuu
19270            111
19271            222
19272            333
19273
19274          + 666
19275            777
19276
19277            000
19278            !!!ˇ»"
19279            .unindent(),
19280    );
19281}
19282
19283#[gpui::test]
19284async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19285    init_test(cx, |_| {});
19286
19287    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19288    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19289
19290    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19291    let multi_buffer = cx.new(|cx| {
19292        let mut multibuffer = MultiBuffer::new(ReadWrite);
19293        multibuffer.push_excerpts(
19294            buffer.clone(),
19295            [
19296                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19297                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19298                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19299            ],
19300            cx,
19301        );
19302        multibuffer
19303    });
19304
19305    let editor =
19306        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19307    editor
19308        .update(cx, |editor, _window, cx| {
19309            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19310            editor
19311                .buffer
19312                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19313        })
19314        .unwrap();
19315
19316    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19317    cx.run_until_parked();
19318
19319    cx.update_editor(|editor, window, cx| {
19320        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19321    });
19322    cx.executor().run_until_parked();
19323
19324    // When the start of a hunk coincides with the start of its excerpt,
19325    // the hunk is expanded. When the start of a hunk is earlier than
19326    // the start of its excerpt, the hunk is not expanded.
19327    cx.assert_state_with_diff(
19328        "
19329            ˇaaa
19330          - bbb
19331          + BBB
19332
19333          - ddd
19334          - eee
19335          + DDD
19336          + EEE
19337            fff
19338
19339            iii
19340        "
19341        .unindent(),
19342    );
19343}
19344
19345#[gpui::test]
19346async fn test_edits_around_expanded_insertion_hunks(
19347    executor: BackgroundExecutor,
19348    cx: &mut TestAppContext,
19349) {
19350    init_test(cx, |_| {});
19351
19352    let mut cx = EditorTestContext::new(cx).await;
19353
19354    let diff_base = r#"
19355        use some::mod1;
19356        use some::mod2;
19357
19358        const A: u32 = 42;
19359
19360        fn main() {
19361            println!("hello");
19362
19363            println!("world");
19364        }
19365        "#
19366    .unindent();
19367    executor.run_until_parked();
19368    cx.set_state(
19369        &r#"
19370        use some::mod1;
19371        use some::mod2;
19372
19373        const A: u32 = 42;
19374        const B: u32 = 42;
19375        const C: u32 = 42;
19376        ˇ
19377
19378        fn main() {
19379            println!("hello");
19380
19381            println!("world");
19382        }
19383        "#
19384        .unindent(),
19385    );
19386
19387    cx.set_head_text(&diff_base);
19388    executor.run_until_parked();
19389
19390    cx.update_editor(|editor, window, cx| {
19391        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19392    });
19393    executor.run_until_parked();
19394
19395    cx.assert_state_with_diff(
19396        r#"
19397        use some::mod1;
19398        use some::mod2;
19399
19400        const A: u32 = 42;
19401      + const B: u32 = 42;
19402      + const C: u32 = 42;
19403      + ˇ
19404
19405        fn main() {
19406            println!("hello");
19407
19408            println!("world");
19409        }
19410      "#
19411        .unindent(),
19412    );
19413
19414    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19415    executor.run_until_parked();
19416
19417    cx.assert_state_with_diff(
19418        r#"
19419        use some::mod1;
19420        use some::mod2;
19421
19422        const A: u32 = 42;
19423      + const B: u32 = 42;
19424      + const C: u32 = 42;
19425      + const D: u32 = 42;
19426      + ˇ
19427
19428        fn main() {
19429            println!("hello");
19430
19431            println!("world");
19432        }
19433      "#
19434        .unindent(),
19435    );
19436
19437    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19438    executor.run_until_parked();
19439
19440    cx.assert_state_with_diff(
19441        r#"
19442        use some::mod1;
19443        use some::mod2;
19444
19445        const A: u32 = 42;
19446      + const B: u32 = 42;
19447      + const C: u32 = 42;
19448      + const D: u32 = 42;
19449      + const E: u32 = 42;
19450      + ˇ
19451
19452        fn main() {
19453            println!("hello");
19454
19455            println!("world");
19456        }
19457      "#
19458        .unindent(),
19459    );
19460
19461    cx.update_editor(|editor, window, cx| {
19462        editor.delete_line(&DeleteLine, window, cx);
19463    });
19464    executor.run_until_parked();
19465
19466    cx.assert_state_with_diff(
19467        r#"
19468        use some::mod1;
19469        use some::mod2;
19470
19471        const A: u32 = 42;
19472      + const B: u32 = 42;
19473      + const C: u32 = 42;
19474      + const D: u32 = 42;
19475      + const E: u32 = 42;
19476        ˇ
19477        fn main() {
19478            println!("hello");
19479
19480            println!("world");
19481        }
19482      "#
19483        .unindent(),
19484    );
19485
19486    cx.update_editor(|editor, window, cx| {
19487        editor.move_up(&MoveUp, window, cx);
19488        editor.delete_line(&DeleteLine, window, cx);
19489        editor.move_up(&MoveUp, window, cx);
19490        editor.delete_line(&DeleteLine, window, cx);
19491        editor.move_up(&MoveUp, window, cx);
19492        editor.delete_line(&DeleteLine, window, cx);
19493    });
19494    executor.run_until_parked();
19495    cx.assert_state_with_diff(
19496        r#"
19497        use some::mod1;
19498        use some::mod2;
19499
19500        const A: u32 = 42;
19501      + const B: u32 = 42;
19502        ˇ
19503        fn main() {
19504            println!("hello");
19505
19506            println!("world");
19507        }
19508      "#
19509        .unindent(),
19510    );
19511
19512    cx.update_editor(|editor, window, cx| {
19513        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19514        editor.delete_line(&DeleteLine, window, cx);
19515    });
19516    executor.run_until_parked();
19517    cx.assert_state_with_diff(
19518        r#"
19519        ˇ
19520        fn main() {
19521            println!("hello");
19522
19523            println!("world");
19524        }
19525      "#
19526        .unindent(),
19527    );
19528}
19529
19530#[gpui::test]
19531async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19532    init_test(cx, |_| {});
19533
19534    let mut cx = EditorTestContext::new(cx).await;
19535    cx.set_head_text(indoc! { "
19536        one
19537        two
19538        three
19539        four
19540        five
19541        "
19542    });
19543    cx.set_state(indoc! { "
19544        one
19545        ˇthree
19546        five
19547    "});
19548    cx.run_until_parked();
19549    cx.update_editor(|editor, window, cx| {
19550        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19551    });
19552    cx.assert_state_with_diff(
19553        indoc! { "
19554        one
19555      - two
19556        ˇthree
19557      - four
19558        five
19559    "}
19560        .to_string(),
19561    );
19562    cx.update_editor(|editor, window, cx| {
19563        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19564    });
19565
19566    cx.assert_state_with_diff(
19567        indoc! { "
19568        one
19569        ˇthree
19570        five
19571    "}
19572        .to_string(),
19573    );
19574
19575    cx.set_state(indoc! { "
19576        one
19577        ˇTWO
19578        three
19579        four
19580        five
19581    "});
19582    cx.run_until_parked();
19583    cx.update_editor(|editor, window, cx| {
19584        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19585    });
19586
19587    cx.assert_state_with_diff(
19588        indoc! { "
19589            one
19590          - two
19591          + ˇTWO
19592            three
19593            four
19594            five
19595        "}
19596        .to_string(),
19597    );
19598    cx.update_editor(|editor, window, cx| {
19599        editor.move_up(&Default::default(), window, cx);
19600        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19601    });
19602    cx.assert_state_with_diff(
19603        indoc! { "
19604            one
19605            ˇTWO
19606            three
19607            four
19608            five
19609        "}
19610        .to_string(),
19611    );
19612}
19613
19614#[gpui::test]
19615async fn test_edits_around_expanded_deletion_hunks(
19616    executor: BackgroundExecutor,
19617    cx: &mut TestAppContext,
19618) {
19619    init_test(cx, |_| {});
19620
19621    let mut cx = EditorTestContext::new(cx).await;
19622
19623    let diff_base = r#"
19624        use some::mod1;
19625        use some::mod2;
19626
19627        const A: u32 = 42;
19628        const B: u32 = 42;
19629        const C: u32 = 42;
19630
19631
19632        fn main() {
19633            println!("hello");
19634
19635            println!("world");
19636        }
19637    "#
19638    .unindent();
19639    executor.run_until_parked();
19640    cx.set_state(
19641        &r#"
19642        use some::mod1;
19643        use some::mod2;
19644
19645        ˇconst B: u32 = 42;
19646        const C: u32 = 42;
19647
19648
19649        fn main() {
19650            println!("hello");
19651
19652            println!("world");
19653        }
19654        "#
19655        .unindent(),
19656    );
19657
19658    cx.set_head_text(&diff_base);
19659    executor.run_until_parked();
19660
19661    cx.update_editor(|editor, window, cx| {
19662        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19663    });
19664    executor.run_until_parked();
19665
19666    cx.assert_state_with_diff(
19667        r#"
19668        use some::mod1;
19669        use some::mod2;
19670
19671      - const A: u32 = 42;
19672        ˇconst B: u32 = 42;
19673        const C: u32 = 42;
19674
19675
19676        fn main() {
19677            println!("hello");
19678
19679            println!("world");
19680        }
19681      "#
19682        .unindent(),
19683    );
19684
19685    cx.update_editor(|editor, window, cx| {
19686        editor.delete_line(&DeleteLine, window, cx);
19687    });
19688    executor.run_until_parked();
19689    cx.assert_state_with_diff(
19690        r#"
19691        use some::mod1;
19692        use some::mod2;
19693
19694      - const A: u32 = 42;
19695      - const B: u32 = 42;
19696        ˇconst C: u32 = 42;
19697
19698
19699        fn main() {
19700            println!("hello");
19701
19702            println!("world");
19703        }
19704      "#
19705        .unindent(),
19706    );
19707
19708    cx.update_editor(|editor, window, cx| {
19709        editor.delete_line(&DeleteLine, window, cx);
19710    });
19711    executor.run_until_parked();
19712    cx.assert_state_with_diff(
19713        r#"
19714        use some::mod1;
19715        use some::mod2;
19716
19717      - const A: u32 = 42;
19718      - const B: u32 = 42;
19719      - const C: u32 = 42;
19720        ˇ
19721
19722        fn main() {
19723            println!("hello");
19724
19725            println!("world");
19726        }
19727      "#
19728        .unindent(),
19729    );
19730
19731    cx.update_editor(|editor, window, cx| {
19732        editor.handle_input("replacement", window, cx);
19733    });
19734    executor.run_until_parked();
19735    cx.assert_state_with_diff(
19736        r#"
19737        use some::mod1;
19738        use some::mod2;
19739
19740      - const A: u32 = 42;
19741      - const B: u32 = 42;
19742      - const C: u32 = 42;
19743      -
19744      + replacementˇ
19745
19746        fn main() {
19747            println!("hello");
19748
19749            println!("world");
19750        }
19751      "#
19752        .unindent(),
19753    );
19754}
19755
19756#[gpui::test]
19757async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19758    init_test(cx, |_| {});
19759
19760    let mut cx = EditorTestContext::new(cx).await;
19761
19762    let base_text = r#"
19763        one
19764        two
19765        three
19766        four
19767        five
19768    "#
19769    .unindent();
19770    executor.run_until_parked();
19771    cx.set_state(
19772        &r#"
19773        one
19774        two
19775        fˇour
19776        five
19777        "#
19778        .unindent(),
19779    );
19780
19781    cx.set_head_text(&base_text);
19782    executor.run_until_parked();
19783
19784    cx.update_editor(|editor, window, cx| {
19785        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19786    });
19787    executor.run_until_parked();
19788
19789    cx.assert_state_with_diff(
19790        r#"
19791          one
19792          two
19793        - three
19794          fˇour
19795          five
19796        "#
19797        .unindent(),
19798    );
19799
19800    cx.update_editor(|editor, window, cx| {
19801        editor.backspace(&Backspace, window, cx);
19802        editor.backspace(&Backspace, window, cx);
19803    });
19804    executor.run_until_parked();
19805    cx.assert_state_with_diff(
19806        r#"
19807          one
19808          two
19809        - threeˇ
19810        - four
19811        + our
19812          five
19813        "#
19814        .unindent(),
19815    );
19816}
19817
19818#[gpui::test]
19819async fn test_edit_after_expanded_modification_hunk(
19820    executor: BackgroundExecutor,
19821    cx: &mut TestAppContext,
19822) {
19823    init_test(cx, |_| {});
19824
19825    let mut cx = EditorTestContext::new(cx).await;
19826
19827    let diff_base = r#"
19828        use some::mod1;
19829        use some::mod2;
19830
19831        const A: u32 = 42;
19832        const B: u32 = 42;
19833        const C: u32 = 42;
19834        const D: u32 = 42;
19835
19836
19837        fn main() {
19838            println!("hello");
19839
19840            println!("world");
19841        }"#
19842    .unindent();
19843
19844    cx.set_state(
19845        &r#"
19846        use some::mod1;
19847        use some::mod2;
19848
19849        const A: u32 = 42;
19850        const B: u32 = 42;
19851        const C: u32 = 43ˇ
19852        const D: u32 = 42;
19853
19854
19855        fn main() {
19856            println!("hello");
19857
19858            println!("world");
19859        }"#
19860        .unindent(),
19861    );
19862
19863    cx.set_head_text(&diff_base);
19864    executor.run_until_parked();
19865    cx.update_editor(|editor, window, cx| {
19866        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19867    });
19868    executor.run_until_parked();
19869
19870    cx.assert_state_with_diff(
19871        r#"
19872        use some::mod1;
19873        use some::mod2;
19874
19875        const A: u32 = 42;
19876        const B: u32 = 42;
19877      - const C: u32 = 42;
19878      + const C: u32 = 43ˇ
19879        const D: u32 = 42;
19880
19881
19882        fn main() {
19883            println!("hello");
19884
19885            println!("world");
19886        }"#
19887        .unindent(),
19888    );
19889
19890    cx.update_editor(|editor, window, cx| {
19891        editor.handle_input("\nnew_line\n", window, cx);
19892    });
19893    executor.run_until_parked();
19894
19895    cx.assert_state_with_diff(
19896        r#"
19897        use some::mod1;
19898        use some::mod2;
19899
19900        const A: u32 = 42;
19901        const B: u32 = 42;
19902      - const C: u32 = 42;
19903      + const C: u32 = 43
19904      + new_line
19905      + ˇ
19906        const D: u32 = 42;
19907
19908
19909        fn main() {
19910            println!("hello");
19911
19912            println!("world");
19913        }"#
19914        .unindent(),
19915    );
19916}
19917
19918#[gpui::test]
19919async fn test_stage_and_unstage_added_file_hunk(
19920    executor: BackgroundExecutor,
19921    cx: &mut TestAppContext,
19922) {
19923    init_test(cx, |_| {});
19924
19925    let mut cx = EditorTestContext::new(cx).await;
19926    cx.update_editor(|editor, _, cx| {
19927        editor.set_expand_all_diff_hunks(cx);
19928    });
19929
19930    let working_copy = r#"
19931            ˇfn main() {
19932                println!("hello, world!");
19933            }
19934        "#
19935    .unindent();
19936
19937    cx.set_state(&working_copy);
19938    executor.run_until_parked();
19939
19940    cx.assert_state_with_diff(
19941        r#"
19942            + ˇfn main() {
19943            +     println!("hello, world!");
19944            + }
19945        "#
19946        .unindent(),
19947    );
19948    cx.assert_index_text(None);
19949
19950    cx.update_editor(|editor, window, cx| {
19951        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19952    });
19953    executor.run_until_parked();
19954    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
19955    cx.assert_state_with_diff(
19956        r#"
19957            + ˇfn main() {
19958            +     println!("hello, world!");
19959            + }
19960        "#
19961        .unindent(),
19962    );
19963
19964    cx.update_editor(|editor, window, cx| {
19965        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19966    });
19967    executor.run_until_parked();
19968    cx.assert_index_text(None);
19969}
19970
19971async fn setup_indent_guides_editor(
19972    text: &str,
19973    cx: &mut TestAppContext,
19974) -> (BufferId, EditorTestContext) {
19975    init_test(cx, |_| {});
19976
19977    let mut cx = EditorTestContext::new(cx).await;
19978
19979    let buffer_id = cx.update_editor(|editor, window, cx| {
19980        editor.set_text(text, window, cx);
19981        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
19982
19983        buffer_ids[0]
19984    });
19985
19986    (buffer_id, cx)
19987}
19988
19989fn assert_indent_guides(
19990    range: Range<u32>,
19991    expected: Vec<IndentGuide>,
19992    active_indices: Option<Vec<usize>>,
19993    cx: &mut EditorTestContext,
19994) {
19995    let indent_guides = cx.update_editor(|editor, window, cx| {
19996        let snapshot = editor.snapshot(window, cx).display_snapshot;
19997        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
19998            editor,
19999            MultiBufferRow(range.start)..MultiBufferRow(range.end),
20000            true,
20001            &snapshot,
20002            cx,
20003        );
20004
20005        indent_guides.sort_by(|a, b| {
20006            a.depth.cmp(&b.depth).then(
20007                a.start_row
20008                    .cmp(&b.start_row)
20009                    .then(a.end_row.cmp(&b.end_row)),
20010            )
20011        });
20012        indent_guides
20013    });
20014
20015    if let Some(expected) = active_indices {
20016        let active_indices = cx.update_editor(|editor, window, cx| {
20017            let snapshot = editor.snapshot(window, cx).display_snapshot;
20018            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
20019        });
20020
20021        assert_eq!(
20022            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
20023            expected,
20024            "Active indent guide indices do not match"
20025        );
20026    }
20027
20028    assert_eq!(indent_guides, expected, "Indent guides do not match");
20029}
20030
20031fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
20032    IndentGuide {
20033        buffer_id,
20034        start_row: MultiBufferRow(start_row),
20035        end_row: MultiBufferRow(end_row),
20036        depth,
20037        tab_size: 4,
20038        settings: IndentGuideSettings {
20039            enabled: true,
20040            line_width: 1,
20041            active_line_width: 1,
20042            coloring: IndentGuideColoring::default(),
20043            background_coloring: IndentGuideBackgroundColoring::default(),
20044        },
20045    }
20046}
20047
20048#[gpui::test]
20049async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
20050    let (buffer_id, mut cx) = setup_indent_guides_editor(
20051        &"
20052        fn main() {
20053            let a = 1;
20054        }"
20055        .unindent(),
20056        cx,
20057    )
20058    .await;
20059
20060    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20061}
20062
20063#[gpui::test]
20064async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
20065    let (buffer_id, mut cx) = setup_indent_guides_editor(
20066        &"
20067        fn main() {
20068            let a = 1;
20069            let b = 2;
20070        }"
20071        .unindent(),
20072        cx,
20073    )
20074    .await;
20075
20076    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20077}
20078
20079#[gpui::test]
20080async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20081    let (buffer_id, mut cx) = setup_indent_guides_editor(
20082        &"
20083        fn main() {
20084            let a = 1;
20085            if a == 3 {
20086                let b = 2;
20087            } else {
20088                let c = 3;
20089            }
20090        }"
20091        .unindent(),
20092        cx,
20093    )
20094    .await;
20095
20096    assert_indent_guides(
20097        0..8,
20098        vec![
20099            indent_guide(buffer_id, 1, 6, 0),
20100            indent_guide(buffer_id, 3, 3, 1),
20101            indent_guide(buffer_id, 5, 5, 1),
20102        ],
20103        None,
20104        &mut cx,
20105    );
20106}
20107
20108#[gpui::test]
20109async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20110    let (buffer_id, mut cx) = setup_indent_guides_editor(
20111        &"
20112        fn main() {
20113            let a = 1;
20114                let b = 2;
20115            let c = 3;
20116        }"
20117        .unindent(),
20118        cx,
20119    )
20120    .await;
20121
20122    assert_indent_guides(
20123        0..5,
20124        vec![
20125            indent_guide(buffer_id, 1, 3, 0),
20126            indent_guide(buffer_id, 2, 2, 1),
20127        ],
20128        None,
20129        &mut cx,
20130    );
20131}
20132
20133#[gpui::test]
20134async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20135    let (buffer_id, mut cx) = setup_indent_guides_editor(
20136        &"
20137        fn main() {
20138            let a = 1;
20139
20140            let c = 3;
20141        }"
20142        .unindent(),
20143        cx,
20144    )
20145    .await;
20146
20147    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20148}
20149
20150#[gpui::test]
20151async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20152    let (buffer_id, mut cx) = setup_indent_guides_editor(
20153        &"
20154        fn main() {
20155            let a = 1;
20156
20157            let c = 3;
20158
20159            if a == 3 {
20160                let b = 2;
20161            } else {
20162                let c = 3;
20163            }
20164        }"
20165        .unindent(),
20166        cx,
20167    )
20168    .await;
20169
20170    assert_indent_guides(
20171        0..11,
20172        vec![
20173            indent_guide(buffer_id, 1, 9, 0),
20174            indent_guide(buffer_id, 6, 6, 1),
20175            indent_guide(buffer_id, 8, 8, 1),
20176        ],
20177        None,
20178        &mut cx,
20179    );
20180}
20181
20182#[gpui::test]
20183async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20184    let (buffer_id, mut cx) = setup_indent_guides_editor(
20185        &"
20186        fn main() {
20187            let a = 1;
20188
20189            let c = 3;
20190
20191            if a == 3 {
20192                let b = 2;
20193            } else {
20194                let c = 3;
20195            }
20196        }"
20197        .unindent(),
20198        cx,
20199    )
20200    .await;
20201
20202    assert_indent_guides(
20203        1..11,
20204        vec![
20205            indent_guide(buffer_id, 1, 9, 0),
20206            indent_guide(buffer_id, 6, 6, 1),
20207            indent_guide(buffer_id, 8, 8, 1),
20208        ],
20209        None,
20210        &mut cx,
20211    );
20212}
20213
20214#[gpui::test]
20215async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20216    let (buffer_id, mut cx) = setup_indent_guides_editor(
20217        &"
20218        fn main() {
20219            let a = 1;
20220
20221            let c = 3;
20222
20223            if a == 3 {
20224                let b = 2;
20225            } else {
20226                let c = 3;
20227            }
20228        }"
20229        .unindent(),
20230        cx,
20231    )
20232    .await;
20233
20234    assert_indent_guides(
20235        1..10,
20236        vec![
20237            indent_guide(buffer_id, 1, 9, 0),
20238            indent_guide(buffer_id, 6, 6, 1),
20239            indent_guide(buffer_id, 8, 8, 1),
20240        ],
20241        None,
20242        &mut cx,
20243    );
20244}
20245
20246#[gpui::test]
20247async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20248    let (buffer_id, mut cx) = setup_indent_guides_editor(
20249        &"
20250        fn main() {
20251            if a {
20252                b(
20253                    c,
20254                    d,
20255                )
20256            } else {
20257                e(
20258                    f
20259                )
20260            }
20261        }"
20262        .unindent(),
20263        cx,
20264    )
20265    .await;
20266
20267    assert_indent_guides(
20268        0..11,
20269        vec![
20270            indent_guide(buffer_id, 1, 10, 0),
20271            indent_guide(buffer_id, 2, 5, 1),
20272            indent_guide(buffer_id, 7, 9, 1),
20273            indent_guide(buffer_id, 3, 4, 2),
20274            indent_guide(buffer_id, 8, 8, 2),
20275        ],
20276        None,
20277        &mut cx,
20278    );
20279
20280    cx.update_editor(|editor, window, cx| {
20281        editor.fold_at(MultiBufferRow(2), window, cx);
20282        assert_eq!(
20283            editor.display_text(cx),
20284            "
20285            fn main() {
20286                if a {
20287                    b(⋯
20288                    )
20289                } else {
20290                    e(
20291                        f
20292                    )
20293                }
20294            }"
20295            .unindent()
20296        );
20297    });
20298
20299    assert_indent_guides(
20300        0..11,
20301        vec![
20302            indent_guide(buffer_id, 1, 10, 0),
20303            indent_guide(buffer_id, 2, 5, 1),
20304            indent_guide(buffer_id, 7, 9, 1),
20305            indent_guide(buffer_id, 8, 8, 2),
20306        ],
20307        None,
20308        &mut cx,
20309    );
20310}
20311
20312#[gpui::test]
20313async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20314    let (buffer_id, mut cx) = setup_indent_guides_editor(
20315        &"
20316        block1
20317            block2
20318                block3
20319                    block4
20320            block2
20321        block1
20322        block1"
20323            .unindent(),
20324        cx,
20325    )
20326    .await;
20327
20328    assert_indent_guides(
20329        1..10,
20330        vec![
20331            indent_guide(buffer_id, 1, 4, 0),
20332            indent_guide(buffer_id, 2, 3, 1),
20333            indent_guide(buffer_id, 3, 3, 2),
20334        ],
20335        None,
20336        &mut cx,
20337    );
20338}
20339
20340#[gpui::test]
20341async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20342    let (buffer_id, mut cx) = setup_indent_guides_editor(
20343        &"
20344        block1
20345            block2
20346                block3
20347
20348        block1
20349        block1"
20350            .unindent(),
20351        cx,
20352    )
20353    .await;
20354
20355    assert_indent_guides(
20356        0..6,
20357        vec![
20358            indent_guide(buffer_id, 1, 2, 0),
20359            indent_guide(buffer_id, 2, 2, 1),
20360        ],
20361        None,
20362        &mut cx,
20363    );
20364}
20365
20366#[gpui::test]
20367async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20368    let (buffer_id, mut cx) = setup_indent_guides_editor(
20369        &"
20370        function component() {
20371        \treturn (
20372        \t\t\t
20373        \t\t<div>
20374        \t\t\t<abc></abc>
20375        \t\t</div>
20376        \t)
20377        }"
20378        .unindent(),
20379        cx,
20380    )
20381    .await;
20382
20383    assert_indent_guides(
20384        0..8,
20385        vec![
20386            indent_guide(buffer_id, 1, 6, 0),
20387            indent_guide(buffer_id, 2, 5, 1),
20388            indent_guide(buffer_id, 4, 4, 2),
20389        ],
20390        None,
20391        &mut cx,
20392    );
20393}
20394
20395#[gpui::test]
20396async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20397    let (buffer_id, mut cx) = setup_indent_guides_editor(
20398        &"
20399        function component() {
20400        \treturn (
20401        \t
20402        \t\t<div>
20403        \t\t\t<abc></abc>
20404        \t\t</div>
20405        \t)
20406        }"
20407        .unindent(),
20408        cx,
20409    )
20410    .await;
20411
20412    assert_indent_guides(
20413        0..8,
20414        vec![
20415            indent_guide(buffer_id, 1, 6, 0),
20416            indent_guide(buffer_id, 2, 5, 1),
20417            indent_guide(buffer_id, 4, 4, 2),
20418        ],
20419        None,
20420        &mut cx,
20421    );
20422}
20423
20424#[gpui::test]
20425async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20426    let (buffer_id, mut cx) = setup_indent_guides_editor(
20427        &"
20428        block1
20429
20430
20431
20432            block2
20433        "
20434        .unindent(),
20435        cx,
20436    )
20437    .await;
20438
20439    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20440}
20441
20442#[gpui::test]
20443async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20444    let (buffer_id, mut cx) = setup_indent_guides_editor(
20445        &"
20446        def a:
20447        \tb = 3
20448        \tif True:
20449        \t\tc = 4
20450        \t\td = 5
20451        \tprint(b)
20452        "
20453        .unindent(),
20454        cx,
20455    )
20456    .await;
20457
20458    assert_indent_guides(
20459        0..6,
20460        vec![
20461            indent_guide(buffer_id, 1, 5, 0),
20462            indent_guide(buffer_id, 3, 4, 1),
20463        ],
20464        None,
20465        &mut cx,
20466    );
20467}
20468
20469#[gpui::test]
20470async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20471    let (buffer_id, mut cx) = setup_indent_guides_editor(
20472        &"
20473    fn main() {
20474        let a = 1;
20475    }"
20476        .unindent(),
20477        cx,
20478    )
20479    .await;
20480
20481    cx.update_editor(|editor, window, cx| {
20482        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20483            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20484        });
20485    });
20486
20487    assert_indent_guides(
20488        0..3,
20489        vec![indent_guide(buffer_id, 1, 1, 0)],
20490        Some(vec![0]),
20491        &mut cx,
20492    );
20493}
20494
20495#[gpui::test]
20496async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20497    let (buffer_id, mut cx) = setup_indent_guides_editor(
20498        &"
20499    fn main() {
20500        if 1 == 2 {
20501            let a = 1;
20502        }
20503    }"
20504        .unindent(),
20505        cx,
20506    )
20507    .await;
20508
20509    cx.update_editor(|editor, window, cx| {
20510        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20511            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20512        });
20513    });
20514
20515    assert_indent_guides(
20516        0..4,
20517        vec![
20518            indent_guide(buffer_id, 1, 3, 0),
20519            indent_guide(buffer_id, 2, 2, 1),
20520        ],
20521        Some(vec![1]),
20522        &mut cx,
20523    );
20524
20525    cx.update_editor(|editor, window, cx| {
20526        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20527            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20528        });
20529    });
20530
20531    assert_indent_guides(
20532        0..4,
20533        vec![
20534            indent_guide(buffer_id, 1, 3, 0),
20535            indent_guide(buffer_id, 2, 2, 1),
20536        ],
20537        Some(vec![1]),
20538        &mut cx,
20539    );
20540
20541    cx.update_editor(|editor, window, cx| {
20542        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20543            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20544        });
20545    });
20546
20547    assert_indent_guides(
20548        0..4,
20549        vec![
20550            indent_guide(buffer_id, 1, 3, 0),
20551            indent_guide(buffer_id, 2, 2, 1),
20552        ],
20553        Some(vec![0]),
20554        &mut cx,
20555    );
20556}
20557
20558#[gpui::test]
20559async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
20560    let (buffer_id, mut cx) = setup_indent_guides_editor(
20561        &"
20562    fn main() {
20563        let a = 1;
20564
20565        let b = 2;
20566    }"
20567        .unindent(),
20568        cx,
20569    )
20570    .await;
20571
20572    cx.update_editor(|editor, window, cx| {
20573        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20574            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20575        });
20576    });
20577
20578    assert_indent_guides(
20579        0..5,
20580        vec![indent_guide(buffer_id, 1, 3, 0)],
20581        Some(vec![0]),
20582        &mut cx,
20583    );
20584}
20585
20586#[gpui::test]
20587async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
20588    let (buffer_id, mut cx) = setup_indent_guides_editor(
20589        &"
20590    def m:
20591        a = 1
20592        pass"
20593            .unindent(),
20594        cx,
20595    )
20596    .await;
20597
20598    cx.update_editor(|editor, window, cx| {
20599        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20600            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20601        });
20602    });
20603
20604    assert_indent_guides(
20605        0..3,
20606        vec![indent_guide(buffer_id, 1, 2, 0)],
20607        Some(vec![0]),
20608        &mut cx,
20609    );
20610}
20611
20612#[gpui::test]
20613async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
20614    init_test(cx, |_| {});
20615    let mut cx = EditorTestContext::new(cx).await;
20616    let text = indoc! {
20617        "
20618        impl A {
20619            fn b() {
20620                0;
20621                3;
20622                5;
20623                6;
20624                7;
20625            }
20626        }
20627        "
20628    };
20629    let base_text = indoc! {
20630        "
20631        impl A {
20632            fn b() {
20633                0;
20634                1;
20635                2;
20636                3;
20637                4;
20638            }
20639            fn c() {
20640                5;
20641                6;
20642                7;
20643            }
20644        }
20645        "
20646    };
20647
20648    cx.update_editor(|editor, window, cx| {
20649        editor.set_text(text, window, cx);
20650
20651        editor.buffer().update(cx, |multibuffer, cx| {
20652            let buffer = multibuffer.as_singleton().unwrap();
20653            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
20654
20655            multibuffer.set_all_diff_hunks_expanded(cx);
20656            multibuffer.add_diff(diff, cx);
20657
20658            buffer.read(cx).remote_id()
20659        })
20660    });
20661    cx.run_until_parked();
20662
20663    cx.assert_state_with_diff(
20664        indoc! { "
20665          impl A {
20666              fn b() {
20667                  0;
20668        -         1;
20669        -         2;
20670                  3;
20671        -         4;
20672        -     }
20673        -     fn c() {
20674                  5;
20675                  6;
20676                  7;
20677              }
20678          }
20679          ˇ"
20680        }
20681        .to_string(),
20682    );
20683
20684    let mut actual_guides = cx.update_editor(|editor, window, cx| {
20685        editor
20686            .snapshot(window, cx)
20687            .buffer_snapshot
20688            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
20689            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
20690            .collect::<Vec<_>>()
20691    });
20692    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
20693    assert_eq!(
20694        actual_guides,
20695        vec![
20696            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
20697            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
20698            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
20699        ]
20700    );
20701}
20702
20703#[gpui::test]
20704async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20705    init_test(cx, |_| {});
20706    let mut cx = EditorTestContext::new(cx).await;
20707
20708    let diff_base = r#"
20709        a
20710        b
20711        c
20712        "#
20713    .unindent();
20714
20715    cx.set_state(
20716        &r#"
20717        ˇA
20718        b
20719        C
20720        "#
20721        .unindent(),
20722    );
20723    cx.set_head_text(&diff_base);
20724    cx.update_editor(|editor, window, cx| {
20725        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20726    });
20727    executor.run_until_parked();
20728
20729    let both_hunks_expanded = r#"
20730        - a
20731        + ˇA
20732          b
20733        - c
20734        + C
20735        "#
20736    .unindent();
20737
20738    cx.assert_state_with_diff(both_hunks_expanded.clone());
20739
20740    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20741        let snapshot = editor.snapshot(window, cx);
20742        let hunks = editor
20743            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20744            .collect::<Vec<_>>();
20745        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20746        let buffer_id = hunks[0].buffer_id;
20747        hunks
20748            .into_iter()
20749            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20750            .collect::<Vec<_>>()
20751    });
20752    assert_eq!(hunk_ranges.len(), 2);
20753
20754    cx.update_editor(|editor, _, cx| {
20755        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20756    });
20757    executor.run_until_parked();
20758
20759    let second_hunk_expanded = r#"
20760          ˇA
20761          b
20762        - c
20763        + C
20764        "#
20765    .unindent();
20766
20767    cx.assert_state_with_diff(second_hunk_expanded);
20768
20769    cx.update_editor(|editor, _, cx| {
20770        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20771    });
20772    executor.run_until_parked();
20773
20774    cx.assert_state_with_diff(both_hunks_expanded.clone());
20775
20776    cx.update_editor(|editor, _, cx| {
20777        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20778    });
20779    executor.run_until_parked();
20780
20781    let first_hunk_expanded = r#"
20782        - a
20783        + ˇA
20784          b
20785          C
20786        "#
20787    .unindent();
20788
20789    cx.assert_state_with_diff(first_hunk_expanded);
20790
20791    cx.update_editor(|editor, _, cx| {
20792        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20793    });
20794    executor.run_until_parked();
20795
20796    cx.assert_state_with_diff(both_hunks_expanded);
20797
20798    cx.set_state(
20799        &r#"
20800        ˇA
20801        b
20802        "#
20803        .unindent(),
20804    );
20805    cx.run_until_parked();
20806
20807    // TODO this cursor position seems bad
20808    cx.assert_state_with_diff(
20809        r#"
20810        - ˇa
20811        + A
20812          b
20813        "#
20814        .unindent(),
20815    );
20816
20817    cx.update_editor(|editor, window, cx| {
20818        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20819    });
20820
20821    cx.assert_state_with_diff(
20822        r#"
20823            - ˇa
20824            + A
20825              b
20826            - c
20827            "#
20828        .unindent(),
20829    );
20830
20831    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20832        let snapshot = editor.snapshot(window, cx);
20833        let hunks = editor
20834            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20835            .collect::<Vec<_>>();
20836        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20837        let buffer_id = hunks[0].buffer_id;
20838        hunks
20839            .into_iter()
20840            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20841            .collect::<Vec<_>>()
20842    });
20843    assert_eq!(hunk_ranges.len(), 2);
20844
20845    cx.update_editor(|editor, _, cx| {
20846        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20847    });
20848    executor.run_until_parked();
20849
20850    cx.assert_state_with_diff(
20851        r#"
20852        - ˇa
20853        + A
20854          b
20855        "#
20856        .unindent(),
20857    );
20858}
20859
20860#[gpui::test]
20861async fn test_toggle_deletion_hunk_at_start_of_file(
20862    executor: BackgroundExecutor,
20863    cx: &mut TestAppContext,
20864) {
20865    init_test(cx, |_| {});
20866    let mut cx = EditorTestContext::new(cx).await;
20867
20868    let diff_base = r#"
20869        a
20870        b
20871        c
20872        "#
20873    .unindent();
20874
20875    cx.set_state(
20876        &r#"
20877        ˇb
20878        c
20879        "#
20880        .unindent(),
20881    );
20882    cx.set_head_text(&diff_base);
20883    cx.update_editor(|editor, window, cx| {
20884        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20885    });
20886    executor.run_until_parked();
20887
20888    let hunk_expanded = r#"
20889        - a
20890          ˇb
20891          c
20892        "#
20893    .unindent();
20894
20895    cx.assert_state_with_diff(hunk_expanded.clone());
20896
20897    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20898        let snapshot = editor.snapshot(window, cx);
20899        let hunks = editor
20900            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20901            .collect::<Vec<_>>();
20902        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20903        let buffer_id = hunks[0].buffer_id;
20904        hunks
20905            .into_iter()
20906            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20907            .collect::<Vec<_>>()
20908    });
20909    assert_eq!(hunk_ranges.len(), 1);
20910
20911    cx.update_editor(|editor, _, cx| {
20912        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20913    });
20914    executor.run_until_parked();
20915
20916    let hunk_collapsed = r#"
20917          ˇb
20918          c
20919        "#
20920    .unindent();
20921
20922    cx.assert_state_with_diff(hunk_collapsed);
20923
20924    cx.update_editor(|editor, _, cx| {
20925        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20926    });
20927    executor.run_until_parked();
20928
20929    cx.assert_state_with_diff(hunk_expanded);
20930}
20931
20932#[gpui::test]
20933async fn test_display_diff_hunks(cx: &mut TestAppContext) {
20934    init_test(cx, |_| {});
20935
20936    let fs = FakeFs::new(cx.executor());
20937    fs.insert_tree(
20938        path!("/test"),
20939        json!({
20940            ".git": {},
20941            "file-1": "ONE\n",
20942            "file-2": "TWO\n",
20943            "file-3": "THREE\n",
20944        }),
20945    )
20946    .await;
20947
20948    fs.set_head_for_repo(
20949        path!("/test/.git").as_ref(),
20950        &[
20951            ("file-1", "one\n".into()),
20952            ("file-2", "two\n".into()),
20953            ("file-3", "three\n".into()),
20954        ],
20955        "deadbeef",
20956    );
20957
20958    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
20959    let mut buffers = vec![];
20960    for i in 1..=3 {
20961        let buffer = project
20962            .update(cx, |project, cx| {
20963                let path = format!(path!("/test/file-{}"), i);
20964                project.open_local_buffer(path, cx)
20965            })
20966            .await
20967            .unwrap();
20968        buffers.push(buffer);
20969    }
20970
20971    let multibuffer = cx.new(|cx| {
20972        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
20973        multibuffer.set_all_diff_hunks_expanded(cx);
20974        for buffer in &buffers {
20975            let snapshot = buffer.read(cx).snapshot();
20976            multibuffer.set_excerpts_for_path(
20977                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
20978                buffer.clone(),
20979                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
20980                2,
20981                cx,
20982            );
20983        }
20984        multibuffer
20985    });
20986
20987    let editor = cx.add_window(|window, cx| {
20988        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
20989    });
20990    cx.run_until_parked();
20991
20992    let snapshot = editor
20993        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
20994        .unwrap();
20995    let hunks = snapshot
20996        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
20997        .map(|hunk| match hunk {
20998            DisplayDiffHunk::Unfolded {
20999                display_row_range, ..
21000            } => display_row_range,
21001            DisplayDiffHunk::Folded { .. } => unreachable!(),
21002        })
21003        .collect::<Vec<_>>();
21004    assert_eq!(
21005        hunks,
21006        [
21007            DisplayRow(2)..DisplayRow(4),
21008            DisplayRow(7)..DisplayRow(9),
21009            DisplayRow(12)..DisplayRow(14),
21010        ]
21011    );
21012}
21013
21014#[gpui::test]
21015async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
21016    init_test(cx, |_| {});
21017
21018    let mut cx = EditorTestContext::new(cx).await;
21019    cx.set_head_text(indoc! { "
21020        one
21021        two
21022        three
21023        four
21024        five
21025        "
21026    });
21027    cx.set_index_text(indoc! { "
21028        one
21029        two
21030        three
21031        four
21032        five
21033        "
21034    });
21035    cx.set_state(indoc! {"
21036        one
21037        TWO
21038        ˇTHREE
21039        FOUR
21040        five
21041    "});
21042    cx.run_until_parked();
21043    cx.update_editor(|editor, window, cx| {
21044        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21045    });
21046    cx.run_until_parked();
21047    cx.assert_index_text(Some(indoc! {"
21048        one
21049        TWO
21050        THREE
21051        FOUR
21052        five
21053    "}));
21054    cx.set_state(indoc! { "
21055        one
21056        TWO
21057        ˇTHREE-HUNDRED
21058        FOUR
21059        five
21060    "});
21061    cx.run_until_parked();
21062    cx.update_editor(|editor, window, cx| {
21063        let snapshot = editor.snapshot(window, cx);
21064        let hunks = editor
21065            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
21066            .collect::<Vec<_>>();
21067        assert_eq!(hunks.len(), 1);
21068        assert_eq!(
21069            hunks[0].status(),
21070            DiffHunkStatus {
21071                kind: DiffHunkStatusKind::Modified,
21072                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21073            }
21074        );
21075
21076        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21077    });
21078    cx.run_until_parked();
21079    cx.assert_index_text(Some(indoc! {"
21080        one
21081        TWO
21082        THREE-HUNDRED
21083        FOUR
21084        five
21085    "}));
21086}
21087
21088#[gpui::test]
21089fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21090    init_test(cx, |_| {});
21091
21092    let editor = cx.add_window(|window, cx| {
21093        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21094        build_editor(buffer, window, cx)
21095    });
21096
21097    let render_args = Arc::new(Mutex::new(None));
21098    let snapshot = editor
21099        .update(cx, |editor, window, cx| {
21100            let snapshot = editor.buffer().read(cx).snapshot(cx);
21101            let range =
21102                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21103
21104            struct RenderArgs {
21105                row: MultiBufferRow,
21106                folded: bool,
21107                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21108            }
21109
21110            let crease = Crease::inline(
21111                range,
21112                FoldPlaceholder::test(),
21113                {
21114                    let toggle_callback = render_args.clone();
21115                    move |row, folded, callback, _window, _cx| {
21116                        *toggle_callback.lock() = Some(RenderArgs {
21117                            row,
21118                            folded,
21119                            callback,
21120                        });
21121                        div()
21122                    }
21123                },
21124                |_row, _folded, _window, _cx| div(),
21125            );
21126
21127            editor.insert_creases(Some(crease), cx);
21128            let snapshot = editor.snapshot(window, cx);
21129            let _div =
21130                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21131            snapshot
21132        })
21133        .unwrap();
21134
21135    let render_args = render_args.lock().take().unwrap();
21136    assert_eq!(render_args.row, MultiBufferRow(1));
21137    assert!(!render_args.folded);
21138    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21139
21140    cx.update_window(*editor, |_, window, cx| {
21141        (render_args.callback)(true, window, cx)
21142    })
21143    .unwrap();
21144    let snapshot = editor
21145        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21146        .unwrap();
21147    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21148
21149    cx.update_window(*editor, |_, window, cx| {
21150        (render_args.callback)(false, window, cx)
21151    })
21152    .unwrap();
21153    let snapshot = editor
21154        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21155        .unwrap();
21156    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21157}
21158
21159#[gpui::test]
21160async fn test_input_text(cx: &mut TestAppContext) {
21161    init_test(cx, |_| {});
21162    let mut cx = EditorTestContext::new(cx).await;
21163
21164    cx.set_state(
21165        &r#"ˇone
21166        two
21167
21168        three
21169        fourˇ
21170        five
21171
21172        siˇx"#
21173            .unindent(),
21174    );
21175
21176    cx.dispatch_action(HandleInput(String::new()));
21177    cx.assert_editor_state(
21178        &r#"ˇone
21179        two
21180
21181        three
21182        fourˇ
21183        five
21184
21185        siˇx"#
21186            .unindent(),
21187    );
21188
21189    cx.dispatch_action(HandleInput("AAAA".to_string()));
21190    cx.assert_editor_state(
21191        &r#"AAAAˇone
21192        two
21193
21194        three
21195        fourAAAAˇ
21196        five
21197
21198        siAAAAˇx"#
21199            .unindent(),
21200    );
21201}
21202
21203#[gpui::test]
21204async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
21205    init_test(cx, |_| {});
21206
21207    let mut cx = EditorTestContext::new(cx).await;
21208    cx.set_state(
21209        r#"let foo = 1;
21210let foo = 2;
21211let foo = 3;
21212let fooˇ = 4;
21213let foo = 5;
21214let foo = 6;
21215let foo = 7;
21216let foo = 8;
21217let foo = 9;
21218let foo = 10;
21219let foo = 11;
21220let foo = 12;
21221let foo = 13;
21222let foo = 14;
21223let foo = 15;"#,
21224    );
21225
21226    cx.update_editor(|e, window, cx| {
21227        assert_eq!(
21228            e.next_scroll_position,
21229            NextScrollCursorCenterTopBottom::Center,
21230            "Default next scroll direction is center",
21231        );
21232
21233        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21234        assert_eq!(
21235            e.next_scroll_position,
21236            NextScrollCursorCenterTopBottom::Top,
21237            "After center, next scroll direction should be top",
21238        );
21239
21240        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21241        assert_eq!(
21242            e.next_scroll_position,
21243            NextScrollCursorCenterTopBottom::Bottom,
21244            "After top, next scroll direction should be bottom",
21245        );
21246
21247        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21248        assert_eq!(
21249            e.next_scroll_position,
21250            NextScrollCursorCenterTopBottom::Center,
21251            "After bottom, scrolling should start over",
21252        );
21253
21254        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21255        assert_eq!(
21256            e.next_scroll_position,
21257            NextScrollCursorCenterTopBottom::Top,
21258            "Scrolling continues if retriggered fast enough"
21259        );
21260    });
21261
21262    cx.executor()
21263        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
21264    cx.executor().run_until_parked();
21265    cx.update_editor(|e, _, _| {
21266        assert_eq!(
21267            e.next_scroll_position,
21268            NextScrollCursorCenterTopBottom::Center,
21269            "If scrolling is not triggered fast enough, it should reset"
21270        );
21271    });
21272}
21273
21274#[gpui::test]
21275async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
21276    init_test(cx, |_| {});
21277    let mut cx = EditorLspTestContext::new_rust(
21278        lsp::ServerCapabilities {
21279            definition_provider: Some(lsp::OneOf::Left(true)),
21280            references_provider: Some(lsp::OneOf::Left(true)),
21281            ..lsp::ServerCapabilities::default()
21282        },
21283        cx,
21284    )
21285    .await;
21286
21287    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21288        let go_to_definition = cx
21289            .lsp
21290            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21291                move |params, _| async move {
21292                    if empty_go_to_definition {
21293                        Ok(None)
21294                    } else {
21295                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21296                            uri: params.text_document_position_params.text_document.uri,
21297                            range: lsp::Range::new(
21298                                lsp::Position::new(4, 3),
21299                                lsp::Position::new(4, 6),
21300                            ),
21301                        })))
21302                    }
21303                },
21304            );
21305        let references = cx
21306            .lsp
21307            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21308                Ok(Some(vec![lsp::Location {
21309                    uri: params.text_document_position.text_document.uri,
21310                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21311                }]))
21312            });
21313        (go_to_definition, references)
21314    };
21315
21316    cx.set_state(
21317        &r#"fn one() {
21318            let mut a = ˇtwo();
21319        }
21320
21321        fn two() {}"#
21322            .unindent(),
21323    );
21324    set_up_lsp_handlers(false, &mut cx);
21325    let navigated = cx
21326        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21327        .await
21328        .expect("Failed to navigate to definition");
21329    assert_eq!(
21330        navigated,
21331        Navigated::Yes,
21332        "Should have navigated to definition from the GetDefinition response"
21333    );
21334    cx.assert_editor_state(
21335        &r#"fn one() {
21336            let mut a = two();
21337        }
21338
21339        fn «twoˇ»() {}"#
21340            .unindent(),
21341    );
21342
21343    let editors = cx.update_workspace(|workspace, _, cx| {
21344        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21345    });
21346    cx.update_editor(|_, _, test_editor_cx| {
21347        assert_eq!(
21348            editors.len(),
21349            1,
21350            "Initially, only one, test, editor should be open in the workspace"
21351        );
21352        assert_eq!(
21353            test_editor_cx.entity(),
21354            editors.last().expect("Asserted len is 1").clone()
21355        );
21356    });
21357
21358    set_up_lsp_handlers(true, &mut cx);
21359    let navigated = cx
21360        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21361        .await
21362        .expect("Failed to navigate to lookup references");
21363    assert_eq!(
21364        navigated,
21365        Navigated::Yes,
21366        "Should have navigated to references as a fallback after empty GoToDefinition response"
21367    );
21368    // We should not change the selections in the existing file,
21369    // if opening another milti buffer with the references
21370    cx.assert_editor_state(
21371        &r#"fn one() {
21372            let mut a = two();
21373        }
21374
21375        fn «twoˇ»() {}"#
21376            .unindent(),
21377    );
21378    let editors = cx.update_workspace(|workspace, _, cx| {
21379        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21380    });
21381    cx.update_editor(|_, _, test_editor_cx| {
21382        assert_eq!(
21383            editors.len(),
21384            2,
21385            "After falling back to references search, we open a new editor with the results"
21386        );
21387        let references_fallback_text = editors
21388            .into_iter()
21389            .find(|new_editor| *new_editor != test_editor_cx.entity())
21390            .expect("Should have one non-test editor now")
21391            .read(test_editor_cx)
21392            .text(test_editor_cx);
21393        assert_eq!(
21394            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21395            "Should use the range from the references response and not the GoToDefinition one"
21396        );
21397    });
21398}
21399
21400#[gpui::test]
21401async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21402    init_test(cx, |_| {});
21403    cx.update(|cx| {
21404        let mut editor_settings = EditorSettings::get_global(cx).clone();
21405        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21406        EditorSettings::override_global(editor_settings, cx);
21407    });
21408    let mut cx = EditorLspTestContext::new_rust(
21409        lsp::ServerCapabilities {
21410            definition_provider: Some(lsp::OneOf::Left(true)),
21411            references_provider: Some(lsp::OneOf::Left(true)),
21412            ..lsp::ServerCapabilities::default()
21413        },
21414        cx,
21415    )
21416    .await;
21417    let original_state = r#"fn one() {
21418        let mut a = ˇtwo();
21419    }
21420
21421    fn two() {}"#
21422        .unindent();
21423    cx.set_state(&original_state);
21424
21425    let mut go_to_definition = cx
21426        .lsp
21427        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21428            move |_, _| async move { Ok(None) },
21429        );
21430    let _references = cx
21431        .lsp
21432        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21433            panic!("Should not call for references with no go to definition fallback")
21434        });
21435
21436    let navigated = cx
21437        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21438        .await
21439        .expect("Failed to navigate to lookup references");
21440    go_to_definition
21441        .next()
21442        .await
21443        .expect("Should have called the go_to_definition handler");
21444
21445    assert_eq!(
21446        navigated,
21447        Navigated::No,
21448        "Should have navigated to references as a fallback after empty GoToDefinition response"
21449    );
21450    cx.assert_editor_state(&original_state);
21451    let editors = cx.update_workspace(|workspace, _, cx| {
21452        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21453    });
21454    cx.update_editor(|_, _, _| {
21455        assert_eq!(
21456            editors.len(),
21457            1,
21458            "After unsuccessful fallback, no other editor should have been opened"
21459        );
21460    });
21461}
21462
21463#[gpui::test]
21464async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
21465    init_test(cx, |_| {});
21466    let mut cx = EditorLspTestContext::new_rust(
21467        lsp::ServerCapabilities {
21468            references_provider: Some(lsp::OneOf::Left(true)),
21469            ..lsp::ServerCapabilities::default()
21470        },
21471        cx,
21472    )
21473    .await;
21474
21475    cx.set_state(
21476        &r#"
21477        fn one() {
21478            let mut a = two();
21479        }
21480
21481        fn ˇtwo() {}"#
21482            .unindent(),
21483    );
21484    cx.lsp
21485        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21486            Ok(Some(vec![
21487                lsp::Location {
21488                    uri: params.text_document_position.text_document.uri.clone(),
21489                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21490                },
21491                lsp::Location {
21492                    uri: params.text_document_position.text_document.uri,
21493                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
21494                },
21495            ]))
21496        });
21497    let navigated = cx
21498        .update_editor(|editor, window, cx| {
21499            editor.find_all_references(&FindAllReferences, window, cx)
21500        })
21501        .unwrap()
21502        .await
21503        .expect("Failed to navigate to references");
21504    assert_eq!(
21505        navigated,
21506        Navigated::Yes,
21507        "Should have navigated to references from the FindAllReferences response"
21508    );
21509    cx.assert_editor_state(
21510        &r#"fn one() {
21511            let mut a = two();
21512        }
21513
21514        fn ˇtwo() {}"#
21515            .unindent(),
21516    );
21517
21518    let editors = cx.update_workspace(|workspace, _, cx| {
21519        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21520    });
21521    cx.update_editor(|_, _, _| {
21522        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
21523    });
21524
21525    cx.set_state(
21526        &r#"fn one() {
21527            let mut a = ˇtwo();
21528        }
21529
21530        fn two() {}"#
21531            .unindent(),
21532    );
21533    let navigated = cx
21534        .update_editor(|editor, window, cx| {
21535            editor.find_all_references(&FindAllReferences, window, cx)
21536        })
21537        .unwrap()
21538        .await
21539        .expect("Failed to navigate to references");
21540    assert_eq!(
21541        navigated,
21542        Navigated::Yes,
21543        "Should have navigated to references from the FindAllReferences response"
21544    );
21545    cx.assert_editor_state(
21546        &r#"fn one() {
21547            let mut a = ˇtwo();
21548        }
21549
21550        fn two() {}"#
21551            .unindent(),
21552    );
21553    let editors = cx.update_workspace(|workspace, _, cx| {
21554        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21555    });
21556    cx.update_editor(|_, _, _| {
21557        assert_eq!(
21558            editors.len(),
21559            2,
21560            "should have re-used the previous multibuffer"
21561        );
21562    });
21563
21564    cx.set_state(
21565        &r#"fn one() {
21566            let mut a = ˇtwo();
21567        }
21568        fn three() {}
21569        fn two() {}"#
21570            .unindent(),
21571    );
21572    cx.lsp
21573        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21574            Ok(Some(vec![
21575                lsp::Location {
21576                    uri: params.text_document_position.text_document.uri.clone(),
21577                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21578                },
21579                lsp::Location {
21580                    uri: params.text_document_position.text_document.uri,
21581                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
21582                },
21583            ]))
21584        });
21585    let navigated = cx
21586        .update_editor(|editor, window, cx| {
21587            editor.find_all_references(&FindAllReferences, window, cx)
21588        })
21589        .unwrap()
21590        .await
21591        .expect("Failed to navigate to references");
21592    assert_eq!(
21593        navigated,
21594        Navigated::Yes,
21595        "Should have navigated to references from the FindAllReferences response"
21596    );
21597    cx.assert_editor_state(
21598        &r#"fn one() {
21599                let mut a = ˇtwo();
21600            }
21601            fn three() {}
21602            fn two() {}"#
21603            .unindent(),
21604    );
21605    let editors = cx.update_workspace(|workspace, _, cx| {
21606        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21607    });
21608    cx.update_editor(|_, _, _| {
21609        assert_eq!(
21610            editors.len(),
21611            3,
21612            "should have used a new multibuffer as offsets changed"
21613        );
21614    });
21615}
21616#[gpui::test]
21617async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
21618    init_test(cx, |_| {});
21619
21620    let language = Arc::new(Language::new(
21621        LanguageConfig::default(),
21622        Some(tree_sitter_rust::LANGUAGE.into()),
21623    ));
21624
21625    let text = r#"
21626        #[cfg(test)]
21627        mod tests() {
21628            #[test]
21629            fn runnable_1() {
21630                let a = 1;
21631            }
21632
21633            #[test]
21634            fn runnable_2() {
21635                let a = 1;
21636                let b = 2;
21637            }
21638        }
21639    "#
21640    .unindent();
21641
21642    let fs = FakeFs::new(cx.executor());
21643    fs.insert_file("/file.rs", Default::default()).await;
21644
21645    let project = Project::test(fs, ["/a".as_ref()], cx).await;
21646    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21647    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21648    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
21649    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
21650
21651    let editor = cx.new_window_entity(|window, cx| {
21652        Editor::new(
21653            EditorMode::full(),
21654            multi_buffer,
21655            Some(project.clone()),
21656            window,
21657            cx,
21658        )
21659    });
21660
21661    editor.update_in(cx, |editor, window, cx| {
21662        let snapshot = editor.buffer().read(cx).snapshot(cx);
21663        editor.tasks.insert(
21664            (buffer.read(cx).remote_id(), 3),
21665            RunnableTasks {
21666                templates: vec![],
21667                offset: snapshot.anchor_before(43),
21668                column: 0,
21669                extra_variables: HashMap::default(),
21670                context_range: BufferOffset(43)..BufferOffset(85),
21671            },
21672        );
21673        editor.tasks.insert(
21674            (buffer.read(cx).remote_id(), 8),
21675            RunnableTasks {
21676                templates: vec![],
21677                offset: snapshot.anchor_before(86),
21678                column: 0,
21679                extra_variables: HashMap::default(),
21680                context_range: BufferOffset(86)..BufferOffset(191),
21681            },
21682        );
21683
21684        // Test finding task when cursor is inside function body
21685        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21686            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
21687        });
21688        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21689        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
21690
21691        // Test finding task when cursor is on function name
21692        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21693            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
21694        });
21695        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21696        assert_eq!(row, 8, "Should find task when cursor is on function name");
21697    });
21698}
21699
21700#[gpui::test]
21701async fn test_folding_buffers(cx: &mut TestAppContext) {
21702    init_test(cx, |_| {});
21703
21704    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21705    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
21706    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
21707
21708    let fs = FakeFs::new(cx.executor());
21709    fs.insert_tree(
21710        path!("/a"),
21711        json!({
21712            "first.rs": sample_text_1,
21713            "second.rs": sample_text_2,
21714            "third.rs": sample_text_3,
21715        }),
21716    )
21717    .await;
21718    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21719    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21720    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21721    let worktree = project.update(cx, |project, cx| {
21722        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21723        assert_eq!(worktrees.len(), 1);
21724        worktrees.pop().unwrap()
21725    });
21726    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21727
21728    let buffer_1 = project
21729        .update(cx, |project, cx| {
21730            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
21731        })
21732        .await
21733        .unwrap();
21734    let buffer_2 = project
21735        .update(cx, |project, cx| {
21736            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
21737        })
21738        .await
21739        .unwrap();
21740    let buffer_3 = project
21741        .update(cx, |project, cx| {
21742            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
21743        })
21744        .await
21745        .unwrap();
21746
21747    let multi_buffer = cx.new(|cx| {
21748        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21749        multi_buffer.push_excerpts(
21750            buffer_1.clone(),
21751            [
21752                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21753                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21754                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21755            ],
21756            cx,
21757        );
21758        multi_buffer.push_excerpts(
21759            buffer_2.clone(),
21760            [
21761                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21762                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21763                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21764            ],
21765            cx,
21766        );
21767        multi_buffer.push_excerpts(
21768            buffer_3.clone(),
21769            [
21770                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21771                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21772                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21773            ],
21774            cx,
21775        );
21776        multi_buffer
21777    });
21778    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21779        Editor::new(
21780            EditorMode::full(),
21781            multi_buffer.clone(),
21782            Some(project.clone()),
21783            window,
21784            cx,
21785        )
21786    });
21787
21788    assert_eq!(
21789        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21790        "\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",
21791    );
21792
21793    multi_buffer_editor.update(cx, |editor, cx| {
21794        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21795    });
21796    assert_eq!(
21797        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21798        "\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",
21799        "After folding the first buffer, its text should not be displayed"
21800    );
21801
21802    multi_buffer_editor.update(cx, |editor, cx| {
21803        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21804    });
21805    assert_eq!(
21806        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21807        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
21808        "After folding the second buffer, its text should not be displayed"
21809    );
21810
21811    multi_buffer_editor.update(cx, |editor, cx| {
21812        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21813    });
21814    assert_eq!(
21815        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21816        "\n\n\n\n\n",
21817        "After folding the third buffer, its text should not be displayed"
21818    );
21819
21820    // Emulate selection inside the fold logic, that should work
21821    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21822        editor
21823            .snapshot(window, cx)
21824            .next_line_boundary(Point::new(0, 4));
21825    });
21826
21827    multi_buffer_editor.update(cx, |editor, cx| {
21828        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21829    });
21830    assert_eq!(
21831        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21832        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21833        "After unfolding the second buffer, its text should be displayed"
21834    );
21835
21836    // Typing inside of buffer 1 causes that buffer to be unfolded.
21837    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21838        assert_eq!(
21839            multi_buffer
21840                .read(cx)
21841                .snapshot(cx)
21842                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
21843                .collect::<String>(),
21844            "bbbb"
21845        );
21846        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21847            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
21848        });
21849        editor.handle_input("B", window, cx);
21850    });
21851
21852    assert_eq!(
21853        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21854        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21855        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
21856    );
21857
21858    multi_buffer_editor.update(cx, |editor, cx| {
21859        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21860    });
21861    assert_eq!(
21862        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21863        "\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",
21864        "After unfolding the all buffers, all original text should be displayed"
21865    );
21866}
21867
21868#[gpui::test]
21869async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
21870    init_test(cx, |_| {});
21871
21872    let sample_text_1 = "1111\n2222\n3333".to_string();
21873    let sample_text_2 = "4444\n5555\n6666".to_string();
21874    let sample_text_3 = "7777\n8888\n9999".to_string();
21875
21876    let fs = FakeFs::new(cx.executor());
21877    fs.insert_tree(
21878        path!("/a"),
21879        json!({
21880            "first.rs": sample_text_1,
21881            "second.rs": sample_text_2,
21882            "third.rs": sample_text_3,
21883        }),
21884    )
21885    .await;
21886    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21887    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21888    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21889    let worktree = project.update(cx, |project, cx| {
21890        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21891        assert_eq!(worktrees.len(), 1);
21892        worktrees.pop().unwrap()
21893    });
21894    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21895
21896    let buffer_1 = project
21897        .update(cx, |project, cx| {
21898            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
21899        })
21900        .await
21901        .unwrap();
21902    let buffer_2 = project
21903        .update(cx, |project, cx| {
21904            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
21905        })
21906        .await
21907        .unwrap();
21908    let buffer_3 = project
21909        .update(cx, |project, cx| {
21910            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
21911        })
21912        .await
21913        .unwrap();
21914
21915    let multi_buffer = cx.new(|cx| {
21916        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21917        multi_buffer.push_excerpts(
21918            buffer_1.clone(),
21919            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21920            cx,
21921        );
21922        multi_buffer.push_excerpts(
21923            buffer_2.clone(),
21924            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21925            cx,
21926        );
21927        multi_buffer.push_excerpts(
21928            buffer_3.clone(),
21929            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21930            cx,
21931        );
21932        multi_buffer
21933    });
21934
21935    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21936        Editor::new(
21937            EditorMode::full(),
21938            multi_buffer,
21939            Some(project.clone()),
21940            window,
21941            cx,
21942        )
21943    });
21944
21945    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
21946    assert_eq!(
21947        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21948        full_text,
21949    );
21950
21951    multi_buffer_editor.update(cx, |editor, cx| {
21952        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21953    });
21954    assert_eq!(
21955        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21956        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
21957        "After folding the first buffer, its text should not be displayed"
21958    );
21959
21960    multi_buffer_editor.update(cx, |editor, cx| {
21961        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21962    });
21963
21964    assert_eq!(
21965        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21966        "\n\n\n\n\n\n7777\n8888\n9999",
21967        "After folding the second buffer, its text should not be displayed"
21968    );
21969
21970    multi_buffer_editor.update(cx, |editor, cx| {
21971        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21972    });
21973    assert_eq!(
21974        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21975        "\n\n\n\n\n",
21976        "After folding the third buffer, its text should not be displayed"
21977    );
21978
21979    multi_buffer_editor.update(cx, |editor, cx| {
21980        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21981    });
21982    assert_eq!(
21983        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21984        "\n\n\n\n4444\n5555\n6666\n\n",
21985        "After unfolding the second buffer, its text should be displayed"
21986    );
21987
21988    multi_buffer_editor.update(cx, |editor, cx| {
21989        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
21990    });
21991    assert_eq!(
21992        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21993        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
21994        "After unfolding the first buffer, its text should be displayed"
21995    );
21996
21997    multi_buffer_editor.update(cx, |editor, cx| {
21998        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21999    });
22000    assert_eq!(
22001        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22002        full_text,
22003        "After unfolding all buffers, all original text should be displayed"
22004    );
22005}
22006
22007#[gpui::test]
22008async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
22009    init_test(cx, |_| {});
22010
22011    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22012
22013    let fs = FakeFs::new(cx.executor());
22014    fs.insert_tree(
22015        path!("/a"),
22016        json!({
22017            "main.rs": sample_text,
22018        }),
22019    )
22020    .await;
22021    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22022    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22023    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22024    let worktree = project.update(cx, |project, cx| {
22025        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22026        assert_eq!(worktrees.len(), 1);
22027        worktrees.pop().unwrap()
22028    });
22029    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22030
22031    let buffer_1 = project
22032        .update(cx, |project, cx| {
22033            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22034        })
22035        .await
22036        .unwrap();
22037
22038    let multi_buffer = cx.new(|cx| {
22039        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22040        multi_buffer.push_excerpts(
22041            buffer_1.clone(),
22042            [ExcerptRange::new(
22043                Point::new(0, 0)
22044                    ..Point::new(
22045                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
22046                        0,
22047                    ),
22048            )],
22049            cx,
22050        );
22051        multi_buffer
22052    });
22053    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22054        Editor::new(
22055            EditorMode::full(),
22056            multi_buffer,
22057            Some(project.clone()),
22058            window,
22059            cx,
22060        )
22061    });
22062
22063    let selection_range = Point::new(1, 0)..Point::new(2, 0);
22064    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22065        enum TestHighlight {}
22066        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22067        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
22068        editor.highlight_text::<TestHighlight>(
22069            vec![highlight_range.clone()],
22070            HighlightStyle::color(Hsla::green()),
22071            cx,
22072        );
22073        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22074            s.select_ranges(Some(highlight_range))
22075        });
22076    });
22077
22078    let full_text = format!("\n\n{sample_text}");
22079    assert_eq!(
22080        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22081        full_text,
22082    );
22083}
22084
22085#[gpui::test]
22086async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22087    init_test(cx, |_| {});
22088    cx.update(|cx| {
22089        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22090            "keymaps/default-linux.json",
22091            cx,
22092        )
22093        .unwrap();
22094        cx.bind_keys(default_key_bindings);
22095    });
22096
22097    let (editor, cx) = cx.add_window_view(|window, cx| {
22098        let multi_buffer = MultiBuffer::build_multi(
22099            [
22100                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22101                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22102                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22103                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22104            ],
22105            cx,
22106        );
22107        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22108
22109        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22110        // fold all but the second buffer, so that we test navigating between two
22111        // adjacent folded buffers, as well as folded buffers at the start and
22112        // end the multibuffer
22113        editor.fold_buffer(buffer_ids[0], cx);
22114        editor.fold_buffer(buffer_ids[2], cx);
22115        editor.fold_buffer(buffer_ids[3], cx);
22116
22117        editor
22118    });
22119    cx.simulate_resize(size(px(1000.), px(1000.)));
22120
22121    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22122    cx.assert_excerpts_with_selections(indoc! {"
22123        [EXCERPT]
22124        ˇ[FOLDED]
22125        [EXCERPT]
22126        a1
22127        b1
22128        [EXCERPT]
22129        [FOLDED]
22130        [EXCERPT]
22131        [FOLDED]
22132        "
22133    });
22134    cx.simulate_keystroke("down");
22135    cx.assert_excerpts_with_selections(indoc! {"
22136        [EXCERPT]
22137        [FOLDED]
22138        [EXCERPT]
22139        ˇa1
22140        b1
22141        [EXCERPT]
22142        [FOLDED]
22143        [EXCERPT]
22144        [FOLDED]
22145        "
22146    });
22147    cx.simulate_keystroke("down");
22148    cx.assert_excerpts_with_selections(indoc! {"
22149        [EXCERPT]
22150        [FOLDED]
22151        [EXCERPT]
22152        a1
22153        ˇb1
22154        [EXCERPT]
22155        [FOLDED]
22156        [EXCERPT]
22157        [FOLDED]
22158        "
22159    });
22160    cx.simulate_keystroke("down");
22161    cx.assert_excerpts_with_selections(indoc! {"
22162        [EXCERPT]
22163        [FOLDED]
22164        [EXCERPT]
22165        a1
22166        b1
22167        ˇ[EXCERPT]
22168        [FOLDED]
22169        [EXCERPT]
22170        [FOLDED]
22171        "
22172    });
22173    cx.simulate_keystroke("down");
22174    cx.assert_excerpts_with_selections(indoc! {"
22175        [EXCERPT]
22176        [FOLDED]
22177        [EXCERPT]
22178        a1
22179        b1
22180        [EXCERPT]
22181        ˇ[FOLDED]
22182        [EXCERPT]
22183        [FOLDED]
22184        "
22185    });
22186    for _ in 0..5 {
22187        cx.simulate_keystroke("down");
22188        cx.assert_excerpts_with_selections(indoc! {"
22189            [EXCERPT]
22190            [FOLDED]
22191            [EXCERPT]
22192            a1
22193            b1
22194            [EXCERPT]
22195            [FOLDED]
22196            [EXCERPT]
22197            ˇ[FOLDED]
22198            "
22199        });
22200    }
22201
22202    cx.simulate_keystroke("up");
22203    cx.assert_excerpts_with_selections(indoc! {"
22204        [EXCERPT]
22205        [FOLDED]
22206        [EXCERPT]
22207        a1
22208        b1
22209        [EXCERPT]
22210        ˇ[FOLDED]
22211        [EXCERPT]
22212        [FOLDED]
22213        "
22214    });
22215    cx.simulate_keystroke("up");
22216    cx.assert_excerpts_with_selections(indoc! {"
22217        [EXCERPT]
22218        [FOLDED]
22219        [EXCERPT]
22220        a1
22221        b1
22222        ˇ[EXCERPT]
22223        [FOLDED]
22224        [EXCERPT]
22225        [FOLDED]
22226        "
22227    });
22228    cx.simulate_keystroke("up");
22229    cx.assert_excerpts_with_selections(indoc! {"
22230        [EXCERPT]
22231        [FOLDED]
22232        [EXCERPT]
22233        a1
22234        ˇb1
22235        [EXCERPT]
22236        [FOLDED]
22237        [EXCERPT]
22238        [FOLDED]
22239        "
22240    });
22241    cx.simulate_keystroke("up");
22242    cx.assert_excerpts_with_selections(indoc! {"
22243        [EXCERPT]
22244        [FOLDED]
22245        [EXCERPT]
22246        ˇa1
22247        b1
22248        [EXCERPT]
22249        [FOLDED]
22250        [EXCERPT]
22251        [FOLDED]
22252        "
22253    });
22254    for _ in 0..5 {
22255        cx.simulate_keystroke("up");
22256        cx.assert_excerpts_with_selections(indoc! {"
22257            [EXCERPT]
22258            ˇ[FOLDED]
22259            [EXCERPT]
22260            a1
22261            b1
22262            [EXCERPT]
22263            [FOLDED]
22264            [EXCERPT]
22265            [FOLDED]
22266            "
22267        });
22268    }
22269}
22270
22271#[gpui::test]
22272async fn test_edit_prediction_text(cx: &mut TestAppContext) {
22273    init_test(cx, |_| {});
22274
22275    // Simple insertion
22276    assert_highlighted_edits(
22277        "Hello, world!",
22278        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
22279        true,
22280        cx,
22281        |highlighted_edits, cx| {
22282            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
22283            assert_eq!(highlighted_edits.highlights.len(), 1);
22284            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
22285            assert_eq!(
22286                highlighted_edits.highlights[0].1.background_color,
22287                Some(cx.theme().status().created_background)
22288            );
22289        },
22290    )
22291    .await;
22292
22293    // Replacement
22294    assert_highlighted_edits(
22295        "This is a test.",
22296        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
22297        false,
22298        cx,
22299        |highlighted_edits, cx| {
22300            assert_eq!(highlighted_edits.text, "That is a test.");
22301            assert_eq!(highlighted_edits.highlights.len(), 1);
22302            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
22303            assert_eq!(
22304                highlighted_edits.highlights[0].1.background_color,
22305                Some(cx.theme().status().created_background)
22306            );
22307        },
22308    )
22309    .await;
22310
22311    // Multiple edits
22312    assert_highlighted_edits(
22313        "Hello, world!",
22314        vec![
22315            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
22316            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
22317        ],
22318        false,
22319        cx,
22320        |highlighted_edits, cx| {
22321            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
22322            assert_eq!(highlighted_edits.highlights.len(), 2);
22323            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
22324            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
22325            assert_eq!(
22326                highlighted_edits.highlights[0].1.background_color,
22327                Some(cx.theme().status().created_background)
22328            );
22329            assert_eq!(
22330                highlighted_edits.highlights[1].1.background_color,
22331                Some(cx.theme().status().created_background)
22332            );
22333        },
22334    )
22335    .await;
22336
22337    // Multiple lines with edits
22338    assert_highlighted_edits(
22339        "First line\nSecond line\nThird line\nFourth line",
22340        vec![
22341            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
22342            (
22343                Point::new(2, 0)..Point::new(2, 10),
22344                "New third line".to_string(),
22345            ),
22346            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
22347        ],
22348        false,
22349        cx,
22350        |highlighted_edits, cx| {
22351            assert_eq!(
22352                highlighted_edits.text,
22353                "Second modified\nNew third line\nFourth updated line"
22354            );
22355            assert_eq!(highlighted_edits.highlights.len(), 3);
22356            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
22357            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
22358            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
22359            for highlight in &highlighted_edits.highlights {
22360                assert_eq!(
22361                    highlight.1.background_color,
22362                    Some(cx.theme().status().created_background)
22363                );
22364            }
22365        },
22366    )
22367    .await;
22368}
22369
22370#[gpui::test]
22371async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
22372    init_test(cx, |_| {});
22373
22374    // Deletion
22375    assert_highlighted_edits(
22376        "Hello, world!",
22377        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
22378        true,
22379        cx,
22380        |highlighted_edits, cx| {
22381            assert_eq!(highlighted_edits.text, "Hello, world!");
22382            assert_eq!(highlighted_edits.highlights.len(), 1);
22383            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
22384            assert_eq!(
22385                highlighted_edits.highlights[0].1.background_color,
22386                Some(cx.theme().status().deleted_background)
22387            );
22388        },
22389    )
22390    .await;
22391
22392    // Insertion
22393    assert_highlighted_edits(
22394        "Hello, world!",
22395        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
22396        true,
22397        cx,
22398        |highlighted_edits, cx| {
22399            assert_eq!(highlighted_edits.highlights.len(), 1);
22400            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
22401            assert_eq!(
22402                highlighted_edits.highlights[0].1.background_color,
22403                Some(cx.theme().status().created_background)
22404            );
22405        },
22406    )
22407    .await;
22408}
22409
22410async fn assert_highlighted_edits(
22411    text: &str,
22412    edits: Vec<(Range<Point>, String)>,
22413    include_deletions: bool,
22414    cx: &mut TestAppContext,
22415    assertion_fn: impl Fn(HighlightedText, &App),
22416) {
22417    let window = cx.add_window(|window, cx| {
22418        let buffer = MultiBuffer::build_simple(text, cx);
22419        Editor::new(EditorMode::full(), buffer, None, window, cx)
22420    });
22421    let cx = &mut VisualTestContext::from_window(*window, cx);
22422
22423    let (buffer, snapshot) = window
22424        .update(cx, |editor, _window, cx| {
22425            (
22426                editor.buffer().clone(),
22427                editor.buffer().read(cx).snapshot(cx),
22428            )
22429        })
22430        .unwrap();
22431
22432    let edits = edits
22433        .into_iter()
22434        .map(|(range, edit)| {
22435            (
22436                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
22437                edit,
22438            )
22439        })
22440        .collect::<Vec<_>>();
22441
22442    let text_anchor_edits = edits
22443        .clone()
22444        .into_iter()
22445        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
22446        .collect::<Vec<_>>();
22447
22448    let edit_preview = window
22449        .update(cx, |_, _window, cx| {
22450            buffer
22451                .read(cx)
22452                .as_singleton()
22453                .unwrap()
22454                .read(cx)
22455                .preview_edits(text_anchor_edits.into(), cx)
22456        })
22457        .unwrap()
22458        .await;
22459
22460    cx.update(|_window, cx| {
22461        let highlighted_edits = edit_prediction_edit_text(
22462            snapshot.as_singleton().unwrap().2,
22463            &edits,
22464            &edit_preview,
22465            include_deletions,
22466            cx,
22467        );
22468        assertion_fn(highlighted_edits, cx)
22469    });
22470}
22471
22472#[track_caller]
22473fn assert_breakpoint(
22474    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
22475    path: &Arc<Path>,
22476    expected: Vec<(u32, Breakpoint)>,
22477) {
22478    if expected.is_empty() {
22479        assert!(!breakpoints.contains_key(path), "{}", path.display());
22480    } else {
22481        let mut breakpoint = breakpoints
22482            .get(path)
22483            .unwrap()
22484            .iter()
22485            .map(|breakpoint| {
22486                (
22487                    breakpoint.row,
22488                    Breakpoint {
22489                        message: breakpoint.message.clone(),
22490                        state: breakpoint.state,
22491                        condition: breakpoint.condition.clone(),
22492                        hit_condition: breakpoint.hit_condition.clone(),
22493                    },
22494                )
22495            })
22496            .collect::<Vec<_>>();
22497
22498        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
22499
22500        assert_eq!(expected, breakpoint);
22501    }
22502}
22503
22504fn add_log_breakpoint_at_cursor(
22505    editor: &mut Editor,
22506    log_message: &str,
22507    window: &mut Window,
22508    cx: &mut Context<Editor>,
22509) {
22510    let (anchor, bp) = editor
22511        .breakpoints_at_cursors(window, cx)
22512        .first()
22513        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
22514        .unwrap_or_else(|| {
22515            let cursor_position: Point = editor.selections.newest(cx).head();
22516
22517            let breakpoint_position = editor
22518                .snapshot(window, cx)
22519                .display_snapshot
22520                .buffer_snapshot
22521                .anchor_before(Point::new(cursor_position.row, 0));
22522
22523            (breakpoint_position, Breakpoint::new_log(log_message))
22524        });
22525
22526    editor.edit_breakpoint_at_anchor(
22527        anchor,
22528        bp,
22529        BreakpointEditAction::EditLogMessage(log_message.into()),
22530        cx,
22531    );
22532}
22533
22534#[gpui::test]
22535async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
22536    init_test(cx, |_| {});
22537
22538    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22539    let fs = FakeFs::new(cx.executor());
22540    fs.insert_tree(
22541        path!("/a"),
22542        json!({
22543            "main.rs": sample_text,
22544        }),
22545    )
22546    .await;
22547    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22548    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22549    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22550
22551    let fs = FakeFs::new(cx.executor());
22552    fs.insert_tree(
22553        path!("/a"),
22554        json!({
22555            "main.rs": sample_text,
22556        }),
22557    )
22558    .await;
22559    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22560    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22561    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22562    let worktree_id = workspace
22563        .update(cx, |workspace, _window, cx| {
22564            workspace.project().update(cx, |project, cx| {
22565                project.worktrees(cx).next().unwrap().read(cx).id()
22566            })
22567        })
22568        .unwrap();
22569
22570    let buffer = project
22571        .update(cx, |project, cx| {
22572            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22573        })
22574        .await
22575        .unwrap();
22576
22577    let (editor, cx) = cx.add_window_view(|window, cx| {
22578        Editor::new(
22579            EditorMode::full(),
22580            MultiBuffer::build_from_buffer(buffer, cx),
22581            Some(project.clone()),
22582            window,
22583            cx,
22584        )
22585    });
22586
22587    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22588    let abs_path = project.read_with(cx, |project, cx| {
22589        project
22590            .absolute_path(&project_path, cx)
22591            .map(Arc::from)
22592            .unwrap()
22593    });
22594
22595    // assert we can add breakpoint on the first line
22596    editor.update_in(cx, |editor, window, cx| {
22597        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22598        editor.move_to_end(&MoveToEnd, window, cx);
22599        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22600    });
22601
22602    let breakpoints = editor.update(cx, |editor, cx| {
22603        editor
22604            .breakpoint_store()
22605            .as_ref()
22606            .unwrap()
22607            .read(cx)
22608            .all_source_breakpoints(cx)
22609    });
22610
22611    assert_eq!(1, breakpoints.len());
22612    assert_breakpoint(
22613        &breakpoints,
22614        &abs_path,
22615        vec![
22616            (0, Breakpoint::new_standard()),
22617            (3, Breakpoint::new_standard()),
22618        ],
22619    );
22620
22621    editor.update_in(cx, |editor, window, cx| {
22622        editor.move_to_beginning(&MoveToBeginning, window, cx);
22623        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22624    });
22625
22626    let breakpoints = editor.update(cx, |editor, cx| {
22627        editor
22628            .breakpoint_store()
22629            .as_ref()
22630            .unwrap()
22631            .read(cx)
22632            .all_source_breakpoints(cx)
22633    });
22634
22635    assert_eq!(1, breakpoints.len());
22636    assert_breakpoint(
22637        &breakpoints,
22638        &abs_path,
22639        vec![(3, Breakpoint::new_standard())],
22640    );
22641
22642    editor.update_in(cx, |editor, window, cx| {
22643        editor.move_to_end(&MoveToEnd, window, cx);
22644        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22645    });
22646
22647    let breakpoints = editor.update(cx, |editor, cx| {
22648        editor
22649            .breakpoint_store()
22650            .as_ref()
22651            .unwrap()
22652            .read(cx)
22653            .all_source_breakpoints(cx)
22654    });
22655
22656    assert_eq!(0, breakpoints.len());
22657    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22658}
22659
22660#[gpui::test]
22661async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
22662    init_test(cx, |_| {});
22663
22664    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22665
22666    let fs = FakeFs::new(cx.executor());
22667    fs.insert_tree(
22668        path!("/a"),
22669        json!({
22670            "main.rs": sample_text,
22671        }),
22672    )
22673    .await;
22674    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22675    let (workspace, cx) =
22676        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22677
22678    let worktree_id = workspace.update(cx, |workspace, cx| {
22679        workspace.project().update(cx, |project, cx| {
22680            project.worktrees(cx).next().unwrap().read(cx).id()
22681        })
22682    });
22683
22684    let buffer = project
22685        .update(cx, |project, cx| {
22686            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22687        })
22688        .await
22689        .unwrap();
22690
22691    let (editor, cx) = cx.add_window_view(|window, cx| {
22692        Editor::new(
22693            EditorMode::full(),
22694            MultiBuffer::build_from_buffer(buffer, cx),
22695            Some(project.clone()),
22696            window,
22697            cx,
22698        )
22699    });
22700
22701    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22702    let abs_path = project.read_with(cx, |project, cx| {
22703        project
22704            .absolute_path(&project_path, cx)
22705            .map(Arc::from)
22706            .unwrap()
22707    });
22708
22709    editor.update_in(cx, |editor, window, cx| {
22710        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22711    });
22712
22713    let breakpoints = editor.update(cx, |editor, cx| {
22714        editor
22715            .breakpoint_store()
22716            .as_ref()
22717            .unwrap()
22718            .read(cx)
22719            .all_source_breakpoints(cx)
22720    });
22721
22722    assert_breakpoint(
22723        &breakpoints,
22724        &abs_path,
22725        vec![(0, Breakpoint::new_log("hello world"))],
22726    );
22727
22728    // Removing a log message from a log breakpoint should remove it
22729    editor.update_in(cx, |editor, window, cx| {
22730        add_log_breakpoint_at_cursor(editor, "", window, cx);
22731    });
22732
22733    let breakpoints = editor.update(cx, |editor, cx| {
22734        editor
22735            .breakpoint_store()
22736            .as_ref()
22737            .unwrap()
22738            .read(cx)
22739            .all_source_breakpoints(cx)
22740    });
22741
22742    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22743
22744    editor.update_in(cx, |editor, window, cx| {
22745        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22746        editor.move_to_end(&MoveToEnd, window, cx);
22747        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22748        // Not adding a log message to a standard breakpoint shouldn't remove it
22749        add_log_breakpoint_at_cursor(editor, "", window, cx);
22750    });
22751
22752    let breakpoints = editor.update(cx, |editor, cx| {
22753        editor
22754            .breakpoint_store()
22755            .as_ref()
22756            .unwrap()
22757            .read(cx)
22758            .all_source_breakpoints(cx)
22759    });
22760
22761    assert_breakpoint(
22762        &breakpoints,
22763        &abs_path,
22764        vec![
22765            (0, Breakpoint::new_standard()),
22766            (3, Breakpoint::new_standard()),
22767        ],
22768    );
22769
22770    editor.update_in(cx, |editor, window, cx| {
22771        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22772    });
22773
22774    let breakpoints = editor.update(cx, |editor, cx| {
22775        editor
22776            .breakpoint_store()
22777            .as_ref()
22778            .unwrap()
22779            .read(cx)
22780            .all_source_breakpoints(cx)
22781    });
22782
22783    assert_breakpoint(
22784        &breakpoints,
22785        &abs_path,
22786        vec![
22787            (0, Breakpoint::new_standard()),
22788            (3, Breakpoint::new_log("hello world")),
22789        ],
22790    );
22791
22792    editor.update_in(cx, |editor, window, cx| {
22793        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
22794    });
22795
22796    let breakpoints = editor.update(cx, |editor, cx| {
22797        editor
22798            .breakpoint_store()
22799            .as_ref()
22800            .unwrap()
22801            .read(cx)
22802            .all_source_breakpoints(cx)
22803    });
22804
22805    assert_breakpoint(
22806        &breakpoints,
22807        &abs_path,
22808        vec![
22809            (0, Breakpoint::new_standard()),
22810            (3, Breakpoint::new_log("hello Earth!!")),
22811        ],
22812    );
22813}
22814
22815/// This also tests that Editor::breakpoint_at_cursor_head is working properly
22816/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
22817/// or when breakpoints were placed out of order. This tests for a regression too
22818#[gpui::test]
22819async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
22820    init_test(cx, |_| {});
22821
22822    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22823    let fs = FakeFs::new(cx.executor());
22824    fs.insert_tree(
22825        path!("/a"),
22826        json!({
22827            "main.rs": sample_text,
22828        }),
22829    )
22830    .await;
22831    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22832    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22833    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22834
22835    let fs = FakeFs::new(cx.executor());
22836    fs.insert_tree(
22837        path!("/a"),
22838        json!({
22839            "main.rs": sample_text,
22840        }),
22841    )
22842    .await;
22843    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22844    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22845    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22846    let worktree_id = workspace
22847        .update(cx, |workspace, _window, cx| {
22848            workspace.project().update(cx, |project, cx| {
22849                project.worktrees(cx).next().unwrap().read(cx).id()
22850            })
22851        })
22852        .unwrap();
22853
22854    let buffer = project
22855        .update(cx, |project, cx| {
22856            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22857        })
22858        .await
22859        .unwrap();
22860
22861    let (editor, cx) = cx.add_window_view(|window, cx| {
22862        Editor::new(
22863            EditorMode::full(),
22864            MultiBuffer::build_from_buffer(buffer, cx),
22865            Some(project.clone()),
22866            window,
22867            cx,
22868        )
22869    });
22870
22871    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22872    let abs_path = project.read_with(cx, |project, cx| {
22873        project
22874            .absolute_path(&project_path, cx)
22875            .map(Arc::from)
22876            .unwrap()
22877    });
22878
22879    // assert we can add breakpoint on the first line
22880    editor.update_in(cx, |editor, window, cx| {
22881        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22882        editor.move_to_end(&MoveToEnd, window, cx);
22883        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22884        editor.move_up(&MoveUp, window, cx);
22885        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22886    });
22887
22888    let breakpoints = editor.update(cx, |editor, cx| {
22889        editor
22890            .breakpoint_store()
22891            .as_ref()
22892            .unwrap()
22893            .read(cx)
22894            .all_source_breakpoints(cx)
22895    });
22896
22897    assert_eq!(1, breakpoints.len());
22898    assert_breakpoint(
22899        &breakpoints,
22900        &abs_path,
22901        vec![
22902            (0, Breakpoint::new_standard()),
22903            (2, Breakpoint::new_standard()),
22904            (3, Breakpoint::new_standard()),
22905        ],
22906    );
22907
22908    editor.update_in(cx, |editor, window, cx| {
22909        editor.move_to_beginning(&MoveToBeginning, window, cx);
22910        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22911        editor.move_to_end(&MoveToEnd, window, cx);
22912        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22913        // Disabling a breakpoint that doesn't exist should do nothing
22914        editor.move_up(&MoveUp, window, cx);
22915        editor.move_up(&MoveUp, window, cx);
22916        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22917    });
22918
22919    let breakpoints = editor.update(cx, |editor, cx| {
22920        editor
22921            .breakpoint_store()
22922            .as_ref()
22923            .unwrap()
22924            .read(cx)
22925            .all_source_breakpoints(cx)
22926    });
22927
22928    let disable_breakpoint = {
22929        let mut bp = Breakpoint::new_standard();
22930        bp.state = BreakpointState::Disabled;
22931        bp
22932    };
22933
22934    assert_eq!(1, breakpoints.len());
22935    assert_breakpoint(
22936        &breakpoints,
22937        &abs_path,
22938        vec![
22939            (0, disable_breakpoint.clone()),
22940            (2, Breakpoint::new_standard()),
22941            (3, disable_breakpoint.clone()),
22942        ],
22943    );
22944
22945    editor.update_in(cx, |editor, window, cx| {
22946        editor.move_to_beginning(&MoveToBeginning, window, cx);
22947        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22948        editor.move_to_end(&MoveToEnd, window, cx);
22949        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22950        editor.move_up(&MoveUp, window, cx);
22951        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22952    });
22953
22954    let breakpoints = editor.update(cx, |editor, cx| {
22955        editor
22956            .breakpoint_store()
22957            .as_ref()
22958            .unwrap()
22959            .read(cx)
22960            .all_source_breakpoints(cx)
22961    });
22962
22963    assert_eq!(1, breakpoints.len());
22964    assert_breakpoint(
22965        &breakpoints,
22966        &abs_path,
22967        vec![
22968            (0, Breakpoint::new_standard()),
22969            (2, disable_breakpoint),
22970            (3, Breakpoint::new_standard()),
22971        ],
22972    );
22973}
22974
22975#[gpui::test]
22976async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
22977    init_test(cx, |_| {});
22978    let capabilities = lsp::ServerCapabilities {
22979        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
22980            prepare_provider: Some(true),
22981            work_done_progress_options: Default::default(),
22982        })),
22983        ..Default::default()
22984    };
22985    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
22986
22987    cx.set_state(indoc! {"
22988        struct Fˇoo {}
22989    "});
22990
22991    cx.update_editor(|editor, _, cx| {
22992        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
22993        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
22994        editor.highlight_background::<DocumentHighlightRead>(
22995            &[highlight_range],
22996            |theme| theme.colors().editor_document_highlight_read_background,
22997            cx,
22998        );
22999    });
23000
23001    let mut prepare_rename_handler = cx
23002        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
23003            move |_, _, _| async move {
23004                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
23005                    start: lsp::Position {
23006                        line: 0,
23007                        character: 7,
23008                    },
23009                    end: lsp::Position {
23010                        line: 0,
23011                        character: 10,
23012                    },
23013                })))
23014            },
23015        );
23016    let prepare_rename_task = cx
23017        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23018        .expect("Prepare rename was not started");
23019    prepare_rename_handler.next().await.unwrap();
23020    prepare_rename_task.await.expect("Prepare rename failed");
23021
23022    let mut rename_handler =
23023        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23024            let edit = lsp::TextEdit {
23025                range: lsp::Range {
23026                    start: lsp::Position {
23027                        line: 0,
23028                        character: 7,
23029                    },
23030                    end: lsp::Position {
23031                        line: 0,
23032                        character: 10,
23033                    },
23034                },
23035                new_text: "FooRenamed".to_string(),
23036            };
23037            Ok(Some(lsp::WorkspaceEdit::new(
23038                // Specify the same edit twice
23039                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
23040            )))
23041        });
23042    let rename_task = cx
23043        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23044        .expect("Confirm rename was not started");
23045    rename_handler.next().await.unwrap();
23046    rename_task.await.expect("Confirm rename failed");
23047    cx.run_until_parked();
23048
23049    // Despite two edits, only one is actually applied as those are identical
23050    cx.assert_editor_state(indoc! {"
23051        struct FooRenamedˇ {}
23052    "});
23053}
23054
23055#[gpui::test]
23056async fn test_rename_without_prepare(cx: &mut TestAppContext) {
23057    init_test(cx, |_| {});
23058    // These capabilities indicate that the server does not support prepare rename.
23059    let capabilities = lsp::ServerCapabilities {
23060        rename_provider: Some(lsp::OneOf::Left(true)),
23061        ..Default::default()
23062    };
23063    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23064
23065    cx.set_state(indoc! {"
23066        struct Fˇoo {}
23067    "});
23068
23069    cx.update_editor(|editor, _window, cx| {
23070        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23071        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23072        editor.highlight_background::<DocumentHighlightRead>(
23073            &[highlight_range],
23074            |theme| theme.colors().editor_document_highlight_read_background,
23075            cx,
23076        );
23077    });
23078
23079    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23080        .expect("Prepare rename was not started")
23081        .await
23082        .expect("Prepare rename failed");
23083
23084    let mut rename_handler =
23085        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23086            let edit = lsp::TextEdit {
23087                range: lsp::Range {
23088                    start: lsp::Position {
23089                        line: 0,
23090                        character: 7,
23091                    },
23092                    end: lsp::Position {
23093                        line: 0,
23094                        character: 10,
23095                    },
23096                },
23097                new_text: "FooRenamed".to_string(),
23098            };
23099            Ok(Some(lsp::WorkspaceEdit::new(
23100                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23101            )))
23102        });
23103    let rename_task = cx
23104        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23105        .expect("Confirm rename was not started");
23106    rename_handler.next().await.unwrap();
23107    rename_task.await.expect("Confirm rename failed");
23108    cx.run_until_parked();
23109
23110    // Correct range is renamed, as `surrounding_word` is used to find it.
23111    cx.assert_editor_state(indoc! {"
23112        struct FooRenamedˇ {}
23113    "});
23114}
23115
23116#[gpui::test]
23117async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23118    init_test(cx, |_| {});
23119    let mut cx = EditorTestContext::new(cx).await;
23120
23121    let language = Arc::new(
23122        Language::new(
23123            LanguageConfig::default(),
23124            Some(tree_sitter_html::LANGUAGE.into()),
23125        )
23126        .with_brackets_query(
23127            r#"
23128            ("<" @open "/>" @close)
23129            ("</" @open ">" @close)
23130            ("<" @open ">" @close)
23131            ("\"" @open "\"" @close)
23132            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23133        "#,
23134        )
23135        .unwrap(),
23136    );
23137    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23138
23139    cx.set_state(indoc! {"
23140        <span>ˇ</span>
23141    "});
23142    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23143    cx.assert_editor_state(indoc! {"
23144        <span>
23145        ˇ
23146        </span>
23147    "});
23148
23149    cx.set_state(indoc! {"
23150        <span><span></span>ˇ</span>
23151    "});
23152    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23153    cx.assert_editor_state(indoc! {"
23154        <span><span></span>
23155        ˇ</span>
23156    "});
23157
23158    cx.set_state(indoc! {"
23159        <span>ˇ
23160        </span>
23161    "});
23162    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23163    cx.assert_editor_state(indoc! {"
23164        <span>
23165        ˇ
23166        </span>
23167    "});
23168}
23169
23170#[gpui::test(iterations = 10)]
23171async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23172    init_test(cx, |_| {});
23173
23174    let fs = FakeFs::new(cx.executor());
23175    fs.insert_tree(
23176        path!("/dir"),
23177        json!({
23178            "a.ts": "a",
23179        }),
23180    )
23181    .await;
23182
23183    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23184    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23185    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23186
23187    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23188    language_registry.add(Arc::new(Language::new(
23189        LanguageConfig {
23190            name: "TypeScript".into(),
23191            matcher: LanguageMatcher {
23192                path_suffixes: vec!["ts".to_string()],
23193                ..Default::default()
23194            },
23195            ..Default::default()
23196        },
23197        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
23198    )));
23199    let mut fake_language_servers = language_registry.register_fake_lsp(
23200        "TypeScript",
23201        FakeLspAdapter {
23202            capabilities: lsp::ServerCapabilities {
23203                code_lens_provider: Some(lsp::CodeLensOptions {
23204                    resolve_provider: Some(true),
23205                }),
23206                execute_command_provider: Some(lsp::ExecuteCommandOptions {
23207                    commands: vec!["_the/command".to_string()],
23208                    ..lsp::ExecuteCommandOptions::default()
23209                }),
23210                ..lsp::ServerCapabilities::default()
23211            },
23212            ..FakeLspAdapter::default()
23213        },
23214    );
23215
23216    let editor = workspace
23217        .update(cx, |workspace, window, cx| {
23218            workspace.open_abs_path(
23219                PathBuf::from(path!("/dir/a.ts")),
23220                OpenOptions::default(),
23221                window,
23222                cx,
23223            )
23224        })
23225        .unwrap()
23226        .await
23227        .unwrap()
23228        .downcast::<Editor>()
23229        .unwrap();
23230    cx.executor().run_until_parked();
23231
23232    let fake_server = fake_language_servers.next().await.unwrap();
23233
23234    let buffer = editor.update(cx, |editor, cx| {
23235        editor
23236            .buffer()
23237            .read(cx)
23238            .as_singleton()
23239            .expect("have opened a single file by path")
23240    });
23241
23242    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
23243    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
23244    drop(buffer_snapshot);
23245    let actions = cx
23246        .update_window(*workspace, |_, window, cx| {
23247            project.code_actions(&buffer, anchor..anchor, window, cx)
23248        })
23249        .unwrap();
23250
23251    fake_server
23252        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23253            Ok(Some(vec![
23254                lsp::CodeLens {
23255                    range: lsp::Range::default(),
23256                    command: Some(lsp::Command {
23257                        title: "Code lens command".to_owned(),
23258                        command: "_the/command".to_owned(),
23259                        arguments: None,
23260                    }),
23261                    data: None,
23262                },
23263                lsp::CodeLens {
23264                    range: lsp::Range::default(),
23265                    command: Some(lsp::Command {
23266                        title: "Command not in capabilities".to_owned(),
23267                        command: "not in capabilities".to_owned(),
23268                        arguments: None,
23269                    }),
23270                    data: None,
23271                },
23272                lsp::CodeLens {
23273                    range: lsp::Range {
23274                        start: lsp::Position {
23275                            line: 1,
23276                            character: 1,
23277                        },
23278                        end: lsp::Position {
23279                            line: 1,
23280                            character: 1,
23281                        },
23282                    },
23283                    command: Some(lsp::Command {
23284                        title: "Command not in range".to_owned(),
23285                        command: "_the/command".to_owned(),
23286                        arguments: None,
23287                    }),
23288                    data: None,
23289                },
23290            ]))
23291        })
23292        .next()
23293        .await;
23294
23295    let actions = actions.await.unwrap();
23296    assert_eq!(
23297        actions.len(),
23298        1,
23299        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
23300    );
23301    let action = actions[0].clone();
23302    let apply = project.update(cx, |project, cx| {
23303        project.apply_code_action(buffer.clone(), action, true, cx)
23304    });
23305
23306    // Resolving the code action does not populate its edits. In absence of
23307    // edits, we must execute the given command.
23308    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
23309        |mut lens, _| async move {
23310            let lens_command = lens.command.as_mut().expect("should have a command");
23311            assert_eq!(lens_command.title, "Code lens command");
23312            lens_command.arguments = Some(vec![json!("the-argument")]);
23313            Ok(lens)
23314        },
23315    );
23316
23317    // While executing the command, the language server sends the editor
23318    // a `workspaceEdit` request.
23319    fake_server
23320        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
23321            let fake = fake_server.clone();
23322            move |params, _| {
23323                assert_eq!(params.command, "_the/command");
23324                let fake = fake.clone();
23325                async move {
23326                    fake.server
23327                        .request::<lsp::request::ApplyWorkspaceEdit>(
23328                            lsp::ApplyWorkspaceEditParams {
23329                                label: None,
23330                                edit: lsp::WorkspaceEdit {
23331                                    changes: Some(
23332                                        [(
23333                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
23334                                            vec![lsp::TextEdit {
23335                                                range: lsp::Range::new(
23336                                                    lsp::Position::new(0, 0),
23337                                                    lsp::Position::new(0, 0),
23338                                                ),
23339                                                new_text: "X".into(),
23340                                            }],
23341                                        )]
23342                                        .into_iter()
23343                                        .collect(),
23344                                    ),
23345                                    ..lsp::WorkspaceEdit::default()
23346                                },
23347                            },
23348                        )
23349                        .await
23350                        .into_response()
23351                        .unwrap();
23352                    Ok(Some(json!(null)))
23353                }
23354            }
23355        })
23356        .next()
23357        .await;
23358
23359    // Applying the code lens command returns a project transaction containing the edits
23360    // sent by the language server in its `workspaceEdit` request.
23361    let transaction = apply.await.unwrap();
23362    assert!(transaction.0.contains_key(&buffer));
23363    buffer.update(cx, |buffer, cx| {
23364        assert_eq!(buffer.text(), "Xa");
23365        buffer.undo(cx);
23366        assert_eq!(buffer.text(), "a");
23367    });
23368
23369    let actions_after_edits = cx
23370        .update_window(*workspace, |_, window, cx| {
23371            project.code_actions(&buffer, anchor..anchor, window, cx)
23372        })
23373        .unwrap()
23374        .await
23375        .unwrap();
23376    assert_eq!(
23377        actions, actions_after_edits,
23378        "For the same selection, same code lens actions should be returned"
23379    );
23380
23381    let _responses =
23382        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23383            panic!("No more code lens requests are expected");
23384        });
23385    editor.update_in(cx, |editor, window, cx| {
23386        editor.select_all(&SelectAll, window, cx);
23387    });
23388    cx.executor().run_until_parked();
23389    let new_actions = cx
23390        .update_window(*workspace, |_, window, cx| {
23391            project.code_actions(&buffer, anchor..anchor, window, cx)
23392        })
23393        .unwrap()
23394        .await
23395        .unwrap();
23396    assert_eq!(
23397        actions, new_actions,
23398        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
23399    );
23400}
23401
23402#[gpui::test]
23403async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
23404    init_test(cx, |_| {});
23405
23406    let fs = FakeFs::new(cx.executor());
23407    let main_text = r#"fn main() {
23408println!("1");
23409println!("2");
23410println!("3");
23411println!("4");
23412println!("5");
23413}"#;
23414    let lib_text = "mod foo {}";
23415    fs.insert_tree(
23416        path!("/a"),
23417        json!({
23418            "lib.rs": lib_text,
23419            "main.rs": main_text,
23420        }),
23421    )
23422    .await;
23423
23424    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23425    let (workspace, cx) =
23426        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23427    let worktree_id = workspace.update(cx, |workspace, cx| {
23428        workspace.project().update(cx, |project, cx| {
23429            project.worktrees(cx).next().unwrap().read(cx).id()
23430        })
23431    });
23432
23433    let expected_ranges = vec![
23434        Point::new(0, 0)..Point::new(0, 0),
23435        Point::new(1, 0)..Point::new(1, 1),
23436        Point::new(2, 0)..Point::new(2, 2),
23437        Point::new(3, 0)..Point::new(3, 3),
23438    ];
23439
23440    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23441    let editor_1 = workspace
23442        .update_in(cx, |workspace, window, cx| {
23443            workspace.open_path(
23444                (worktree_id, rel_path("main.rs")),
23445                Some(pane_1.downgrade()),
23446                true,
23447                window,
23448                cx,
23449            )
23450        })
23451        .unwrap()
23452        .await
23453        .downcast::<Editor>()
23454        .unwrap();
23455    pane_1.update(cx, |pane, cx| {
23456        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23457        open_editor.update(cx, |editor, cx| {
23458            assert_eq!(
23459                editor.display_text(cx),
23460                main_text,
23461                "Original main.rs text on initial open",
23462            );
23463            assert_eq!(
23464                editor
23465                    .selections
23466                    .all::<Point>(cx)
23467                    .into_iter()
23468                    .map(|s| s.range())
23469                    .collect::<Vec<_>>(),
23470                vec![Point::zero()..Point::zero()],
23471                "Default selections on initial open",
23472            );
23473        })
23474    });
23475    editor_1.update_in(cx, |editor, window, cx| {
23476        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23477            s.select_ranges(expected_ranges.clone());
23478        });
23479    });
23480
23481    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
23482        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
23483    });
23484    let editor_2 = workspace
23485        .update_in(cx, |workspace, window, cx| {
23486            workspace.open_path(
23487                (worktree_id, rel_path("main.rs")),
23488                Some(pane_2.downgrade()),
23489                true,
23490                window,
23491                cx,
23492            )
23493        })
23494        .unwrap()
23495        .await
23496        .downcast::<Editor>()
23497        .unwrap();
23498    pane_2.update(cx, |pane, cx| {
23499        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23500        open_editor.update(cx, |editor, cx| {
23501            assert_eq!(
23502                editor.display_text(cx),
23503                main_text,
23504                "Original main.rs text on initial open in another panel",
23505            );
23506            assert_eq!(
23507                editor
23508                    .selections
23509                    .all::<Point>(cx)
23510                    .into_iter()
23511                    .map(|s| s.range())
23512                    .collect::<Vec<_>>(),
23513                vec![Point::zero()..Point::zero()],
23514                "Default selections on initial open in another panel",
23515            );
23516        })
23517    });
23518
23519    editor_2.update_in(cx, |editor, window, cx| {
23520        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23521    });
23522
23523    let _other_editor_1 = workspace
23524        .update_in(cx, |workspace, window, cx| {
23525            workspace.open_path(
23526                (worktree_id, rel_path("lib.rs")),
23527                Some(pane_1.downgrade()),
23528                true,
23529                window,
23530                cx,
23531            )
23532        })
23533        .unwrap()
23534        .await
23535        .downcast::<Editor>()
23536        .unwrap();
23537    pane_1
23538        .update_in(cx, |pane, window, cx| {
23539            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23540        })
23541        .await
23542        .unwrap();
23543    drop(editor_1);
23544    pane_1.update(cx, |pane, cx| {
23545        pane.active_item()
23546            .unwrap()
23547            .downcast::<Editor>()
23548            .unwrap()
23549            .update(cx, |editor, cx| {
23550                assert_eq!(
23551                    editor.display_text(cx),
23552                    lib_text,
23553                    "Other file should be open and active",
23554                );
23555            });
23556        assert_eq!(pane.items().count(), 1, "No other editors should be open");
23557    });
23558
23559    let _other_editor_2 = workspace
23560        .update_in(cx, |workspace, window, cx| {
23561            workspace.open_path(
23562                (worktree_id, rel_path("lib.rs")),
23563                Some(pane_2.downgrade()),
23564                true,
23565                window,
23566                cx,
23567            )
23568        })
23569        .unwrap()
23570        .await
23571        .downcast::<Editor>()
23572        .unwrap();
23573    pane_2
23574        .update_in(cx, |pane, window, cx| {
23575            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23576        })
23577        .await
23578        .unwrap();
23579    drop(editor_2);
23580    pane_2.update(cx, |pane, cx| {
23581        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23582        open_editor.update(cx, |editor, cx| {
23583            assert_eq!(
23584                editor.display_text(cx),
23585                lib_text,
23586                "Other file should be open and active in another panel too",
23587            );
23588        });
23589        assert_eq!(
23590            pane.items().count(),
23591            1,
23592            "No other editors should be open in another pane",
23593        );
23594    });
23595
23596    let _editor_1_reopened = workspace
23597        .update_in(cx, |workspace, window, cx| {
23598            workspace.open_path(
23599                (worktree_id, rel_path("main.rs")),
23600                Some(pane_1.downgrade()),
23601                true,
23602                window,
23603                cx,
23604            )
23605        })
23606        .unwrap()
23607        .await
23608        .downcast::<Editor>()
23609        .unwrap();
23610    let _editor_2_reopened = workspace
23611        .update_in(cx, |workspace, window, cx| {
23612            workspace.open_path(
23613                (worktree_id, rel_path("main.rs")),
23614                Some(pane_2.downgrade()),
23615                true,
23616                window,
23617                cx,
23618            )
23619        })
23620        .unwrap()
23621        .await
23622        .downcast::<Editor>()
23623        .unwrap();
23624    pane_1.update(cx, |pane, cx| {
23625        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23626        open_editor.update(cx, |editor, cx| {
23627            assert_eq!(
23628                editor.display_text(cx),
23629                main_text,
23630                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
23631            );
23632            assert_eq!(
23633                editor
23634                    .selections
23635                    .all::<Point>(cx)
23636                    .into_iter()
23637                    .map(|s| s.range())
23638                    .collect::<Vec<_>>(),
23639                expected_ranges,
23640                "Previous editor in the 1st panel had selections and should get them restored on reopen",
23641            );
23642        })
23643    });
23644    pane_2.update(cx, |pane, cx| {
23645        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23646        open_editor.update(cx, |editor, cx| {
23647            assert_eq!(
23648                editor.display_text(cx),
23649                r#"fn main() {
23650⋯rintln!("1");
23651⋯intln!("2");
23652⋯ntln!("3");
23653println!("4");
23654println!("5");
23655}"#,
23656                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
23657            );
23658            assert_eq!(
23659                editor
23660                    .selections
23661                    .all::<Point>(cx)
23662                    .into_iter()
23663                    .map(|s| s.range())
23664                    .collect::<Vec<_>>(),
23665                vec![Point::zero()..Point::zero()],
23666                "Previous editor in the 2nd pane had no selections changed hence should restore none",
23667            );
23668        })
23669    });
23670}
23671
23672#[gpui::test]
23673async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
23674    init_test(cx, |_| {});
23675
23676    let fs = FakeFs::new(cx.executor());
23677    let main_text = r#"fn main() {
23678println!("1");
23679println!("2");
23680println!("3");
23681println!("4");
23682println!("5");
23683}"#;
23684    let lib_text = "mod foo {}";
23685    fs.insert_tree(
23686        path!("/a"),
23687        json!({
23688            "lib.rs": lib_text,
23689            "main.rs": main_text,
23690        }),
23691    )
23692    .await;
23693
23694    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23695    let (workspace, cx) =
23696        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23697    let worktree_id = workspace.update(cx, |workspace, cx| {
23698        workspace.project().update(cx, |project, cx| {
23699            project.worktrees(cx).next().unwrap().read(cx).id()
23700        })
23701    });
23702
23703    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23704    let editor = workspace
23705        .update_in(cx, |workspace, window, cx| {
23706            workspace.open_path(
23707                (worktree_id, rel_path("main.rs")),
23708                Some(pane.downgrade()),
23709                true,
23710                window,
23711                cx,
23712            )
23713        })
23714        .unwrap()
23715        .await
23716        .downcast::<Editor>()
23717        .unwrap();
23718    pane.update(cx, |pane, cx| {
23719        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23720        open_editor.update(cx, |editor, cx| {
23721            assert_eq!(
23722                editor.display_text(cx),
23723                main_text,
23724                "Original main.rs text on initial open",
23725            );
23726        })
23727    });
23728    editor.update_in(cx, |editor, window, cx| {
23729        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
23730    });
23731
23732    cx.update_global(|store: &mut SettingsStore, cx| {
23733        store.update_user_settings(cx, |s| {
23734            s.workspace.restore_on_file_reopen = Some(false);
23735        });
23736    });
23737    editor.update_in(cx, |editor, window, cx| {
23738        editor.fold_ranges(
23739            vec![
23740                Point::new(1, 0)..Point::new(1, 1),
23741                Point::new(2, 0)..Point::new(2, 2),
23742                Point::new(3, 0)..Point::new(3, 3),
23743            ],
23744            false,
23745            window,
23746            cx,
23747        );
23748    });
23749    pane.update_in(cx, |pane, window, cx| {
23750        pane.close_all_items(&CloseAllItems::default(), window, cx)
23751    })
23752    .await
23753    .unwrap();
23754    pane.update(cx, |pane, _| {
23755        assert!(pane.active_item().is_none());
23756    });
23757    cx.update_global(|store: &mut SettingsStore, cx| {
23758        store.update_user_settings(cx, |s| {
23759            s.workspace.restore_on_file_reopen = Some(true);
23760        });
23761    });
23762
23763    let _editor_reopened = workspace
23764        .update_in(cx, |workspace, window, cx| {
23765            workspace.open_path(
23766                (worktree_id, rel_path("main.rs")),
23767                Some(pane.downgrade()),
23768                true,
23769                window,
23770                cx,
23771            )
23772        })
23773        .unwrap()
23774        .await
23775        .downcast::<Editor>()
23776        .unwrap();
23777    pane.update(cx, |pane, cx| {
23778        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23779        open_editor.update(cx, |editor, cx| {
23780            assert_eq!(
23781                editor.display_text(cx),
23782                main_text,
23783                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
23784            );
23785        })
23786    });
23787}
23788
23789#[gpui::test]
23790async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
23791    struct EmptyModalView {
23792        focus_handle: gpui::FocusHandle,
23793    }
23794    impl EventEmitter<DismissEvent> for EmptyModalView {}
23795    impl Render for EmptyModalView {
23796        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
23797            div()
23798        }
23799    }
23800    impl Focusable for EmptyModalView {
23801        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
23802            self.focus_handle.clone()
23803        }
23804    }
23805    impl workspace::ModalView for EmptyModalView {}
23806    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
23807        EmptyModalView {
23808            focus_handle: cx.focus_handle(),
23809        }
23810    }
23811
23812    init_test(cx, |_| {});
23813
23814    let fs = FakeFs::new(cx.executor());
23815    let project = Project::test(fs, [], cx).await;
23816    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23817    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
23818    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23819    let editor = cx.new_window_entity(|window, cx| {
23820        Editor::new(
23821            EditorMode::full(),
23822            buffer,
23823            Some(project.clone()),
23824            window,
23825            cx,
23826        )
23827    });
23828    workspace
23829        .update(cx, |workspace, window, cx| {
23830            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
23831        })
23832        .unwrap();
23833    editor.update_in(cx, |editor, window, cx| {
23834        editor.open_context_menu(&OpenContextMenu, window, cx);
23835        assert!(editor.mouse_context_menu.is_some());
23836    });
23837    workspace
23838        .update(cx, |workspace, window, cx| {
23839            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
23840        })
23841        .unwrap();
23842    cx.read(|cx| {
23843        assert!(editor.read(cx).mouse_context_menu.is_none());
23844    });
23845}
23846
23847fn set_linked_edit_ranges(
23848    opening: (Point, Point),
23849    closing: (Point, Point),
23850    editor: &mut Editor,
23851    cx: &mut Context<Editor>,
23852) {
23853    let Some((buffer, _)) = editor
23854        .buffer
23855        .read(cx)
23856        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
23857    else {
23858        panic!("Failed to get buffer for selection position");
23859    };
23860    let buffer = buffer.read(cx);
23861    let buffer_id = buffer.remote_id();
23862    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
23863    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
23864    let mut linked_ranges = HashMap::default();
23865    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
23866    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
23867}
23868
23869#[gpui::test]
23870async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
23871    init_test(cx, |_| {});
23872
23873    let fs = FakeFs::new(cx.executor());
23874    fs.insert_file(path!("/file.html"), Default::default())
23875        .await;
23876
23877    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
23878
23879    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23880    let html_language = Arc::new(Language::new(
23881        LanguageConfig {
23882            name: "HTML".into(),
23883            matcher: LanguageMatcher {
23884                path_suffixes: vec!["html".to_string()],
23885                ..LanguageMatcher::default()
23886            },
23887            brackets: BracketPairConfig {
23888                pairs: vec![BracketPair {
23889                    start: "<".into(),
23890                    end: ">".into(),
23891                    close: true,
23892                    ..Default::default()
23893                }],
23894                ..Default::default()
23895            },
23896            ..Default::default()
23897        },
23898        Some(tree_sitter_html::LANGUAGE.into()),
23899    ));
23900    language_registry.add(html_language);
23901    let mut fake_servers = language_registry.register_fake_lsp(
23902        "HTML",
23903        FakeLspAdapter {
23904            capabilities: lsp::ServerCapabilities {
23905                completion_provider: Some(lsp::CompletionOptions {
23906                    resolve_provider: Some(true),
23907                    ..Default::default()
23908                }),
23909                ..Default::default()
23910            },
23911            ..Default::default()
23912        },
23913    );
23914
23915    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23916    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23917
23918    let worktree_id = workspace
23919        .update(cx, |workspace, _window, cx| {
23920            workspace.project().update(cx, |project, cx| {
23921                project.worktrees(cx).next().unwrap().read(cx).id()
23922            })
23923        })
23924        .unwrap();
23925    project
23926        .update(cx, |project, cx| {
23927            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
23928        })
23929        .await
23930        .unwrap();
23931    let editor = workspace
23932        .update(cx, |workspace, window, cx| {
23933            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
23934        })
23935        .unwrap()
23936        .await
23937        .unwrap()
23938        .downcast::<Editor>()
23939        .unwrap();
23940
23941    let fake_server = fake_servers.next().await.unwrap();
23942    editor.update_in(cx, |editor, window, cx| {
23943        editor.set_text("<ad></ad>", window, cx);
23944        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23945            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
23946        });
23947        set_linked_edit_ranges(
23948            (Point::new(0, 1), Point::new(0, 3)),
23949            (Point::new(0, 6), Point::new(0, 8)),
23950            editor,
23951            cx,
23952        );
23953    });
23954    let mut completion_handle =
23955        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
23956            Ok(Some(lsp::CompletionResponse::Array(vec![
23957                lsp::CompletionItem {
23958                    label: "head".to_string(),
23959                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23960                        lsp::InsertReplaceEdit {
23961                            new_text: "head".to_string(),
23962                            insert: lsp::Range::new(
23963                                lsp::Position::new(0, 1),
23964                                lsp::Position::new(0, 3),
23965                            ),
23966                            replace: lsp::Range::new(
23967                                lsp::Position::new(0, 1),
23968                                lsp::Position::new(0, 3),
23969                            ),
23970                        },
23971                    )),
23972                    ..Default::default()
23973                },
23974            ])))
23975        });
23976    editor.update_in(cx, |editor, window, cx| {
23977        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
23978    });
23979    cx.run_until_parked();
23980    completion_handle.next().await.unwrap();
23981    editor.update(cx, |editor, _| {
23982        assert!(
23983            editor.context_menu_visible(),
23984            "Completion menu should be visible"
23985        );
23986    });
23987    editor.update_in(cx, |editor, window, cx| {
23988        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
23989    });
23990    cx.executor().run_until_parked();
23991    editor.update(cx, |editor, cx| {
23992        assert_eq!(editor.text(cx), "<head></head>");
23993    });
23994}
23995
23996#[gpui::test]
23997async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
23998    init_test(cx, |_| {});
23999
24000    let mut cx = EditorTestContext::new(cx).await;
24001    let language = Arc::new(Language::new(
24002        LanguageConfig {
24003            name: "TSX".into(),
24004            matcher: LanguageMatcher {
24005                path_suffixes: vec!["tsx".to_string()],
24006                ..LanguageMatcher::default()
24007            },
24008            brackets: BracketPairConfig {
24009                pairs: vec![BracketPair {
24010                    start: "<".into(),
24011                    end: ">".into(),
24012                    close: true,
24013                    ..Default::default()
24014                }],
24015                ..Default::default()
24016            },
24017            linked_edit_characters: HashSet::from_iter(['.']),
24018            ..Default::default()
24019        },
24020        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
24021    ));
24022    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24023
24024    // Test typing > does not extend linked pair
24025    cx.set_state("<divˇ<div></div>");
24026    cx.update_editor(|editor, _, cx| {
24027        set_linked_edit_ranges(
24028            (Point::new(0, 1), Point::new(0, 4)),
24029            (Point::new(0, 11), Point::new(0, 14)),
24030            editor,
24031            cx,
24032        );
24033    });
24034    cx.update_editor(|editor, window, cx| {
24035        editor.handle_input(">", window, cx);
24036    });
24037    cx.assert_editor_state("<div>ˇ<div></div>");
24038
24039    // Test typing . do extend linked pair
24040    cx.set_state("<Animatedˇ></Animated>");
24041    cx.update_editor(|editor, _, cx| {
24042        set_linked_edit_ranges(
24043            (Point::new(0, 1), Point::new(0, 9)),
24044            (Point::new(0, 12), Point::new(0, 20)),
24045            editor,
24046            cx,
24047        );
24048    });
24049    cx.update_editor(|editor, window, cx| {
24050        editor.handle_input(".", window, cx);
24051    });
24052    cx.assert_editor_state("<Animated.ˇ></Animated.>");
24053    cx.update_editor(|editor, _, cx| {
24054        set_linked_edit_ranges(
24055            (Point::new(0, 1), Point::new(0, 10)),
24056            (Point::new(0, 13), Point::new(0, 21)),
24057            editor,
24058            cx,
24059        );
24060    });
24061    cx.update_editor(|editor, window, cx| {
24062        editor.handle_input("V", window, cx);
24063    });
24064    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
24065}
24066
24067#[gpui::test]
24068async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
24069    init_test(cx, |_| {});
24070
24071    let fs = FakeFs::new(cx.executor());
24072    fs.insert_tree(
24073        path!("/root"),
24074        json!({
24075            "a": {
24076                "main.rs": "fn main() {}",
24077            },
24078            "foo": {
24079                "bar": {
24080                    "external_file.rs": "pub mod external {}",
24081                }
24082            }
24083        }),
24084    )
24085    .await;
24086
24087    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
24088    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24089    language_registry.add(rust_lang());
24090    let _fake_servers = language_registry.register_fake_lsp(
24091        "Rust",
24092        FakeLspAdapter {
24093            ..FakeLspAdapter::default()
24094        },
24095    );
24096    let (workspace, cx) =
24097        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24098    let worktree_id = workspace.update(cx, |workspace, cx| {
24099        workspace.project().update(cx, |project, cx| {
24100            project.worktrees(cx).next().unwrap().read(cx).id()
24101        })
24102    });
24103
24104    let assert_language_servers_count =
24105        |expected: usize, context: &str, cx: &mut VisualTestContext| {
24106            project.update(cx, |project, cx| {
24107                let current = project
24108                    .lsp_store()
24109                    .read(cx)
24110                    .as_local()
24111                    .unwrap()
24112                    .language_servers
24113                    .len();
24114                assert_eq!(expected, current, "{context}");
24115            });
24116        };
24117
24118    assert_language_servers_count(
24119        0,
24120        "No servers should be running before any file is open",
24121        cx,
24122    );
24123    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24124    let main_editor = workspace
24125        .update_in(cx, |workspace, window, cx| {
24126            workspace.open_path(
24127                (worktree_id, rel_path("main.rs")),
24128                Some(pane.downgrade()),
24129                true,
24130                window,
24131                cx,
24132            )
24133        })
24134        .unwrap()
24135        .await
24136        .downcast::<Editor>()
24137        .unwrap();
24138    pane.update(cx, |pane, cx| {
24139        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24140        open_editor.update(cx, |editor, cx| {
24141            assert_eq!(
24142                editor.display_text(cx),
24143                "fn main() {}",
24144                "Original main.rs text on initial open",
24145            );
24146        });
24147        assert_eq!(open_editor, main_editor);
24148    });
24149    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24150
24151    let external_editor = workspace
24152        .update_in(cx, |workspace, window, cx| {
24153            workspace.open_abs_path(
24154                PathBuf::from("/root/foo/bar/external_file.rs"),
24155                OpenOptions::default(),
24156                window,
24157                cx,
24158            )
24159        })
24160        .await
24161        .expect("opening external file")
24162        .downcast::<Editor>()
24163        .expect("downcasted external file's open element to editor");
24164    pane.update(cx, |pane, cx| {
24165        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24166        open_editor.update(cx, |editor, cx| {
24167            assert_eq!(
24168                editor.display_text(cx),
24169                "pub mod external {}",
24170                "External file is open now",
24171            );
24172        });
24173        assert_eq!(open_editor, external_editor);
24174    });
24175    assert_language_servers_count(
24176        1,
24177        "Second, external, *.rs file should join the existing server",
24178        cx,
24179    );
24180
24181    pane.update_in(cx, |pane, window, cx| {
24182        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24183    })
24184    .await
24185    .unwrap();
24186    pane.update_in(cx, |pane, window, cx| {
24187        pane.navigate_backward(&Default::default(), window, cx);
24188    });
24189    cx.run_until_parked();
24190    pane.update(cx, |pane, cx| {
24191        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24192        open_editor.update(cx, |editor, cx| {
24193            assert_eq!(
24194                editor.display_text(cx),
24195                "pub mod external {}",
24196                "External file is open now",
24197            );
24198        });
24199    });
24200    assert_language_servers_count(
24201        1,
24202        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
24203        cx,
24204    );
24205
24206    cx.update(|_, cx| {
24207        workspace::reload(cx);
24208    });
24209    assert_language_servers_count(
24210        1,
24211        "After reloading the worktree with local and external files opened, only one project should be started",
24212        cx,
24213    );
24214}
24215
24216#[gpui::test]
24217async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
24218    init_test(cx, |_| {});
24219
24220    let mut cx = EditorTestContext::new(cx).await;
24221    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24222    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24223
24224    // test cursor move to start of each line on tab
24225    // for `if`, `elif`, `else`, `while`, `with` and `for`
24226    cx.set_state(indoc! {"
24227        def main():
24228        ˇ    for item in items:
24229        ˇ        while item.active:
24230        ˇ            if item.value > 10:
24231        ˇ                continue
24232        ˇ            elif item.value < 0:
24233        ˇ                break
24234        ˇ            else:
24235        ˇ                with item.context() as ctx:
24236        ˇ                    yield count
24237        ˇ        else:
24238        ˇ            log('while else')
24239        ˇ    else:
24240        ˇ        log('for else')
24241    "});
24242    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24243    cx.assert_editor_state(indoc! {"
24244        def main():
24245            ˇfor item in items:
24246                ˇwhile item.active:
24247                    ˇif item.value > 10:
24248                        ˇcontinue
24249                    ˇelif item.value < 0:
24250                        ˇbreak
24251                    ˇelse:
24252                        ˇwith item.context() as ctx:
24253                            ˇyield count
24254                ˇelse:
24255                    ˇlog('while else')
24256            ˇelse:
24257                ˇlog('for else')
24258    "});
24259    // test relative indent is preserved when tab
24260    // for `if`, `elif`, `else`, `while`, `with` and `for`
24261    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24262    cx.assert_editor_state(indoc! {"
24263        def main():
24264                ˇfor item in items:
24265                    ˇwhile item.active:
24266                        ˇif item.value > 10:
24267                            ˇcontinue
24268                        ˇelif item.value < 0:
24269                            ˇbreak
24270                        ˇelse:
24271                            ˇwith item.context() as ctx:
24272                                ˇyield count
24273                    ˇelse:
24274                        ˇlog('while else')
24275                ˇelse:
24276                    ˇlog('for else')
24277    "});
24278
24279    // test cursor move to start of each line on tab
24280    // for `try`, `except`, `else`, `finally`, `match` and `def`
24281    cx.set_state(indoc! {"
24282        def main():
24283        ˇ    try:
24284        ˇ        fetch()
24285        ˇ    except ValueError:
24286        ˇ        handle_error()
24287        ˇ    else:
24288        ˇ        match value:
24289        ˇ            case _:
24290        ˇ    finally:
24291        ˇ        def status():
24292        ˇ            return 0
24293    "});
24294    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24295    cx.assert_editor_state(indoc! {"
24296        def main():
24297            ˇtry:
24298                ˇfetch()
24299            ˇexcept ValueError:
24300                ˇhandle_error()
24301            ˇelse:
24302                ˇmatch value:
24303                    ˇcase _:
24304            ˇfinally:
24305                ˇdef status():
24306                    ˇreturn 0
24307    "});
24308    // test relative indent is preserved when tab
24309    // for `try`, `except`, `else`, `finally`, `match` and `def`
24310    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24311    cx.assert_editor_state(indoc! {"
24312        def main():
24313                ˇtry:
24314                    ˇfetch()
24315                ˇexcept ValueError:
24316                    ˇhandle_error()
24317                ˇelse:
24318                    ˇmatch value:
24319                        ˇcase _:
24320                ˇfinally:
24321                    ˇdef status():
24322                        ˇreturn 0
24323    "});
24324}
24325
24326#[gpui::test]
24327async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
24328    init_test(cx, |_| {});
24329
24330    let mut cx = EditorTestContext::new(cx).await;
24331    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24332    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24333
24334    // test `else` auto outdents when typed inside `if` block
24335    cx.set_state(indoc! {"
24336        def main():
24337            if i == 2:
24338                return
24339                ˇ
24340    "});
24341    cx.update_editor(|editor, window, cx| {
24342        editor.handle_input("else:", window, cx);
24343    });
24344    cx.assert_editor_state(indoc! {"
24345        def main():
24346            if i == 2:
24347                return
24348            else:ˇ
24349    "});
24350
24351    // test `except` auto outdents when typed inside `try` block
24352    cx.set_state(indoc! {"
24353        def main():
24354            try:
24355                i = 2
24356                ˇ
24357    "});
24358    cx.update_editor(|editor, window, cx| {
24359        editor.handle_input("except:", window, cx);
24360    });
24361    cx.assert_editor_state(indoc! {"
24362        def main():
24363            try:
24364                i = 2
24365            except:ˇ
24366    "});
24367
24368    // test `else` auto outdents when typed inside `except` block
24369    cx.set_state(indoc! {"
24370        def main():
24371            try:
24372                i = 2
24373            except:
24374                j = 2
24375                ˇ
24376    "});
24377    cx.update_editor(|editor, window, cx| {
24378        editor.handle_input("else:", window, cx);
24379    });
24380    cx.assert_editor_state(indoc! {"
24381        def main():
24382            try:
24383                i = 2
24384            except:
24385                j = 2
24386            else:ˇ
24387    "});
24388
24389    // test `finally` auto outdents when typed inside `else` block
24390    cx.set_state(indoc! {"
24391        def main():
24392            try:
24393                i = 2
24394            except:
24395                j = 2
24396            else:
24397                k = 2
24398                ˇ
24399    "});
24400    cx.update_editor(|editor, window, cx| {
24401        editor.handle_input("finally:", window, cx);
24402    });
24403    cx.assert_editor_state(indoc! {"
24404        def main():
24405            try:
24406                i = 2
24407            except:
24408                j = 2
24409            else:
24410                k = 2
24411            finally:ˇ
24412    "});
24413
24414    // test `else` does not outdents when typed inside `except` block right after for block
24415    cx.set_state(indoc! {"
24416        def main():
24417            try:
24418                i = 2
24419            except:
24420                for i in range(n):
24421                    pass
24422                ˇ
24423    "});
24424    cx.update_editor(|editor, window, cx| {
24425        editor.handle_input("else:", window, cx);
24426    });
24427    cx.assert_editor_state(indoc! {"
24428        def main():
24429            try:
24430                i = 2
24431            except:
24432                for i in range(n):
24433                    pass
24434                else:ˇ
24435    "});
24436
24437    // test `finally` auto outdents when typed inside `else` block right after for block
24438    cx.set_state(indoc! {"
24439        def main():
24440            try:
24441                i = 2
24442            except:
24443                j = 2
24444            else:
24445                for i in range(n):
24446                    pass
24447                ˇ
24448    "});
24449    cx.update_editor(|editor, window, cx| {
24450        editor.handle_input("finally:", window, cx);
24451    });
24452    cx.assert_editor_state(indoc! {"
24453        def main():
24454            try:
24455                i = 2
24456            except:
24457                j = 2
24458            else:
24459                for i in range(n):
24460                    pass
24461            finally:ˇ
24462    "});
24463
24464    // test `except` outdents to inner "try" block
24465    cx.set_state(indoc! {"
24466        def main():
24467            try:
24468                i = 2
24469                if i == 2:
24470                    try:
24471                        i = 3
24472                        ˇ
24473    "});
24474    cx.update_editor(|editor, window, cx| {
24475        editor.handle_input("except:", window, cx);
24476    });
24477    cx.assert_editor_state(indoc! {"
24478        def main():
24479            try:
24480                i = 2
24481                if i == 2:
24482                    try:
24483                        i = 3
24484                    except:ˇ
24485    "});
24486
24487    // test `except` outdents to outer "try" block
24488    cx.set_state(indoc! {"
24489        def main():
24490            try:
24491                i = 2
24492                if i == 2:
24493                    try:
24494                        i = 3
24495                ˇ
24496    "});
24497    cx.update_editor(|editor, window, cx| {
24498        editor.handle_input("except:", window, cx);
24499    });
24500    cx.assert_editor_state(indoc! {"
24501        def main():
24502            try:
24503                i = 2
24504                if i == 2:
24505                    try:
24506                        i = 3
24507            except:ˇ
24508    "});
24509
24510    // test `else` stays at correct indent when typed after `for` block
24511    cx.set_state(indoc! {"
24512        def main():
24513            for i in range(10):
24514                if i == 3:
24515                    break
24516            ˇ
24517    "});
24518    cx.update_editor(|editor, window, cx| {
24519        editor.handle_input("else:", window, cx);
24520    });
24521    cx.assert_editor_state(indoc! {"
24522        def main():
24523            for i in range(10):
24524                if i == 3:
24525                    break
24526            else:ˇ
24527    "});
24528
24529    // test does not outdent on typing after line with square brackets
24530    cx.set_state(indoc! {"
24531        def f() -> list[str]:
24532            ˇ
24533    "});
24534    cx.update_editor(|editor, window, cx| {
24535        editor.handle_input("a", window, cx);
24536    });
24537    cx.assert_editor_state(indoc! {"
24538        def f() -> list[str]:
2453924540    "});
24541
24542    // test does not outdent on typing : after case keyword
24543    cx.set_state(indoc! {"
24544        match 1:
24545            caseˇ
24546    "});
24547    cx.update_editor(|editor, window, cx| {
24548        editor.handle_input(":", window, cx);
24549    });
24550    cx.assert_editor_state(indoc! {"
24551        match 1:
24552            case:ˇ
24553    "});
24554}
24555
24556#[gpui::test]
24557async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
24558    init_test(cx, |_| {});
24559    update_test_language_settings(cx, |settings| {
24560        settings.defaults.extend_comment_on_newline = Some(false);
24561    });
24562    let mut cx = EditorTestContext::new(cx).await;
24563    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24564    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24565
24566    // test correct indent after newline on comment
24567    cx.set_state(indoc! {"
24568        # COMMENT:ˇ
24569    "});
24570    cx.update_editor(|editor, window, cx| {
24571        editor.newline(&Newline, window, cx);
24572    });
24573    cx.assert_editor_state(indoc! {"
24574        # COMMENT:
24575        ˇ
24576    "});
24577
24578    // test correct indent after newline in brackets
24579    cx.set_state(indoc! {"
24580        {ˇ}
24581    "});
24582    cx.update_editor(|editor, window, cx| {
24583        editor.newline(&Newline, window, cx);
24584    });
24585    cx.run_until_parked();
24586    cx.assert_editor_state(indoc! {"
24587        {
24588            ˇ
24589        }
24590    "});
24591
24592    cx.set_state(indoc! {"
24593        (ˇ)
24594    "});
24595    cx.update_editor(|editor, window, cx| {
24596        editor.newline(&Newline, window, cx);
24597    });
24598    cx.run_until_parked();
24599    cx.assert_editor_state(indoc! {"
24600        (
24601            ˇ
24602        )
24603    "});
24604
24605    // do not indent after empty lists or dictionaries
24606    cx.set_state(indoc! {"
24607        a = []ˇ
24608    "});
24609    cx.update_editor(|editor, window, cx| {
24610        editor.newline(&Newline, window, cx);
24611    });
24612    cx.run_until_parked();
24613    cx.assert_editor_state(indoc! {"
24614        a = []
24615        ˇ
24616    "});
24617}
24618
24619#[gpui::test]
24620async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
24621    init_test(cx, |_| {});
24622
24623    let mut cx = EditorTestContext::new(cx).await;
24624    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24625    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24626
24627    // test cursor move to start of each line on tab
24628    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
24629    cx.set_state(indoc! {"
24630        function main() {
24631        ˇ    for item in $items; do
24632        ˇ        while [ -n \"$item\" ]; do
24633        ˇ            if [ \"$value\" -gt 10 ]; then
24634        ˇ                continue
24635        ˇ            elif [ \"$value\" -lt 0 ]; then
24636        ˇ                break
24637        ˇ            else
24638        ˇ                echo \"$item\"
24639        ˇ            fi
24640        ˇ        done
24641        ˇ    done
24642        ˇ}
24643    "});
24644    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24645    cx.assert_editor_state(indoc! {"
24646        function main() {
24647            ˇfor item in $items; do
24648                ˇwhile [ -n \"$item\" ]; do
24649                    ˇif [ \"$value\" -gt 10 ]; then
24650                        ˇcontinue
24651                    ˇelif [ \"$value\" -lt 0 ]; then
24652                        ˇbreak
24653                    ˇelse
24654                        ˇecho \"$item\"
24655                    ˇfi
24656                ˇdone
24657            ˇdone
24658        ˇ}
24659    "});
24660    // test relative indent is preserved when tab
24661    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24662    cx.assert_editor_state(indoc! {"
24663        function main() {
24664                ˇfor item in $items; do
24665                    ˇwhile [ -n \"$item\" ]; do
24666                        ˇif [ \"$value\" -gt 10 ]; then
24667                            ˇcontinue
24668                        ˇelif [ \"$value\" -lt 0 ]; then
24669                            ˇbreak
24670                        ˇelse
24671                            ˇecho \"$item\"
24672                        ˇfi
24673                    ˇdone
24674                ˇdone
24675            ˇ}
24676    "});
24677
24678    // test cursor move to start of each line on tab
24679    // for `case` statement with patterns
24680    cx.set_state(indoc! {"
24681        function handle() {
24682        ˇ    case \"$1\" in
24683        ˇ        start)
24684        ˇ            echo \"a\"
24685        ˇ            ;;
24686        ˇ        stop)
24687        ˇ            echo \"b\"
24688        ˇ            ;;
24689        ˇ        *)
24690        ˇ            echo \"c\"
24691        ˇ            ;;
24692        ˇ    esac
24693        ˇ}
24694    "});
24695    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24696    cx.assert_editor_state(indoc! {"
24697        function handle() {
24698            ˇcase \"$1\" in
24699                ˇstart)
24700                    ˇecho \"a\"
24701                    ˇ;;
24702                ˇstop)
24703                    ˇecho \"b\"
24704                    ˇ;;
24705                ˇ*)
24706                    ˇecho \"c\"
24707                    ˇ;;
24708            ˇesac
24709        ˇ}
24710    "});
24711}
24712
24713#[gpui::test]
24714async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
24715    init_test(cx, |_| {});
24716
24717    let mut cx = EditorTestContext::new(cx).await;
24718    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24719    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24720
24721    // test indents on comment insert
24722    cx.set_state(indoc! {"
24723        function main() {
24724        ˇ    for item in $items; do
24725        ˇ        while [ -n \"$item\" ]; do
24726        ˇ            if [ \"$value\" -gt 10 ]; then
24727        ˇ                continue
24728        ˇ            elif [ \"$value\" -lt 0 ]; then
24729        ˇ                break
24730        ˇ            else
24731        ˇ                echo \"$item\"
24732        ˇ            fi
24733        ˇ        done
24734        ˇ    done
24735        ˇ}
24736    "});
24737    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
24738    cx.assert_editor_state(indoc! {"
24739        function main() {
24740        #ˇ    for item in $items; do
24741        #ˇ        while [ -n \"$item\" ]; do
24742        #ˇ            if [ \"$value\" -gt 10 ]; then
24743        #ˇ                continue
24744        #ˇ            elif [ \"$value\" -lt 0 ]; then
24745        #ˇ                break
24746        #ˇ            else
24747        #ˇ                echo \"$item\"
24748        #ˇ            fi
24749        #ˇ        done
24750        #ˇ    done
24751        #ˇ}
24752    "});
24753}
24754
24755#[gpui::test]
24756async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
24757    init_test(cx, |_| {});
24758
24759    let mut cx = EditorTestContext::new(cx).await;
24760    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24761    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24762
24763    // test `else` auto outdents when typed inside `if` block
24764    cx.set_state(indoc! {"
24765        if [ \"$1\" = \"test\" ]; then
24766            echo \"foo bar\"
24767            ˇ
24768    "});
24769    cx.update_editor(|editor, window, cx| {
24770        editor.handle_input("else", window, cx);
24771    });
24772    cx.assert_editor_state(indoc! {"
24773        if [ \"$1\" = \"test\" ]; then
24774            echo \"foo bar\"
24775        elseˇ
24776    "});
24777
24778    // test `elif` auto outdents when typed inside `if` block
24779    cx.set_state(indoc! {"
24780        if [ \"$1\" = \"test\" ]; then
24781            echo \"foo bar\"
24782            ˇ
24783    "});
24784    cx.update_editor(|editor, window, cx| {
24785        editor.handle_input("elif", window, cx);
24786    });
24787    cx.assert_editor_state(indoc! {"
24788        if [ \"$1\" = \"test\" ]; then
24789            echo \"foo bar\"
24790        elifˇ
24791    "});
24792
24793    // test `fi` auto outdents when typed inside `else` block
24794    cx.set_state(indoc! {"
24795        if [ \"$1\" = \"test\" ]; then
24796            echo \"foo bar\"
24797        else
24798            echo \"bar baz\"
24799            ˇ
24800    "});
24801    cx.update_editor(|editor, window, cx| {
24802        editor.handle_input("fi", window, cx);
24803    });
24804    cx.assert_editor_state(indoc! {"
24805        if [ \"$1\" = \"test\" ]; then
24806            echo \"foo bar\"
24807        else
24808            echo \"bar baz\"
24809        fiˇ
24810    "});
24811
24812    // test `done` auto outdents when typed inside `while` block
24813    cx.set_state(indoc! {"
24814        while read line; do
24815            echo \"$line\"
24816            ˇ
24817    "});
24818    cx.update_editor(|editor, window, cx| {
24819        editor.handle_input("done", window, cx);
24820    });
24821    cx.assert_editor_state(indoc! {"
24822        while read line; do
24823            echo \"$line\"
24824        doneˇ
24825    "});
24826
24827    // test `done` auto outdents when typed inside `for` block
24828    cx.set_state(indoc! {"
24829        for file in *.txt; do
24830            cat \"$file\"
24831            ˇ
24832    "});
24833    cx.update_editor(|editor, window, cx| {
24834        editor.handle_input("done", window, cx);
24835    });
24836    cx.assert_editor_state(indoc! {"
24837        for file in *.txt; do
24838            cat \"$file\"
24839        doneˇ
24840    "});
24841
24842    // test `esac` auto outdents when typed inside `case` block
24843    cx.set_state(indoc! {"
24844        case \"$1\" in
24845            start)
24846                echo \"foo bar\"
24847                ;;
24848            stop)
24849                echo \"bar baz\"
24850                ;;
24851            ˇ
24852    "});
24853    cx.update_editor(|editor, window, cx| {
24854        editor.handle_input("esac", window, cx);
24855    });
24856    cx.assert_editor_state(indoc! {"
24857        case \"$1\" in
24858            start)
24859                echo \"foo bar\"
24860                ;;
24861            stop)
24862                echo \"bar baz\"
24863                ;;
24864        esacˇ
24865    "});
24866
24867    // test `*)` auto outdents when typed inside `case` block
24868    cx.set_state(indoc! {"
24869        case \"$1\" in
24870            start)
24871                echo \"foo bar\"
24872                ;;
24873                ˇ
24874    "});
24875    cx.update_editor(|editor, window, cx| {
24876        editor.handle_input("*)", window, cx);
24877    });
24878    cx.assert_editor_state(indoc! {"
24879        case \"$1\" in
24880            start)
24881                echo \"foo bar\"
24882                ;;
24883            *)ˇ
24884    "});
24885
24886    // test `fi` outdents to correct level with nested if blocks
24887    cx.set_state(indoc! {"
24888        if [ \"$1\" = \"test\" ]; then
24889            echo \"outer if\"
24890            if [ \"$2\" = \"debug\" ]; then
24891                echo \"inner if\"
24892                ˇ
24893    "});
24894    cx.update_editor(|editor, window, cx| {
24895        editor.handle_input("fi", window, cx);
24896    });
24897    cx.assert_editor_state(indoc! {"
24898        if [ \"$1\" = \"test\" ]; then
24899            echo \"outer if\"
24900            if [ \"$2\" = \"debug\" ]; then
24901                echo \"inner if\"
24902            fiˇ
24903    "});
24904}
24905
24906#[gpui::test]
24907async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
24908    init_test(cx, |_| {});
24909    update_test_language_settings(cx, |settings| {
24910        settings.defaults.extend_comment_on_newline = Some(false);
24911    });
24912    let mut cx = EditorTestContext::new(cx).await;
24913    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24914    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24915
24916    // test correct indent after newline on comment
24917    cx.set_state(indoc! {"
24918        # COMMENT:ˇ
24919    "});
24920    cx.update_editor(|editor, window, cx| {
24921        editor.newline(&Newline, window, cx);
24922    });
24923    cx.assert_editor_state(indoc! {"
24924        # COMMENT:
24925        ˇ
24926    "});
24927
24928    // test correct indent after newline after `then`
24929    cx.set_state(indoc! {"
24930
24931        if [ \"$1\" = \"test\" ]; thenˇ
24932    "});
24933    cx.update_editor(|editor, window, cx| {
24934        editor.newline(&Newline, window, cx);
24935    });
24936    cx.run_until_parked();
24937    cx.assert_editor_state(indoc! {"
24938
24939        if [ \"$1\" = \"test\" ]; then
24940            ˇ
24941    "});
24942
24943    // test correct indent after newline after `else`
24944    cx.set_state(indoc! {"
24945        if [ \"$1\" = \"test\" ]; then
24946        elseˇ
24947    "});
24948    cx.update_editor(|editor, window, cx| {
24949        editor.newline(&Newline, window, cx);
24950    });
24951    cx.run_until_parked();
24952    cx.assert_editor_state(indoc! {"
24953        if [ \"$1\" = \"test\" ]; then
24954        else
24955            ˇ
24956    "});
24957
24958    // test correct indent after newline after `elif`
24959    cx.set_state(indoc! {"
24960        if [ \"$1\" = \"test\" ]; then
24961        elifˇ
24962    "});
24963    cx.update_editor(|editor, window, cx| {
24964        editor.newline(&Newline, window, cx);
24965    });
24966    cx.run_until_parked();
24967    cx.assert_editor_state(indoc! {"
24968        if [ \"$1\" = \"test\" ]; then
24969        elif
24970            ˇ
24971    "});
24972
24973    // test correct indent after newline after `do`
24974    cx.set_state(indoc! {"
24975        for file in *.txt; doˇ
24976    "});
24977    cx.update_editor(|editor, window, cx| {
24978        editor.newline(&Newline, window, cx);
24979    });
24980    cx.run_until_parked();
24981    cx.assert_editor_state(indoc! {"
24982        for file in *.txt; do
24983            ˇ
24984    "});
24985
24986    // test correct indent after newline after case pattern
24987    cx.set_state(indoc! {"
24988        case \"$1\" in
24989            start)ˇ
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        case \"$1\" in
24997            start)
24998                ˇ
24999    "});
25000
25001    // test correct indent after newline after case pattern
25002    cx.set_state(indoc! {"
25003        case \"$1\" in
25004            start)
25005                ;;
25006            *)ˇ
25007    "});
25008    cx.update_editor(|editor, window, cx| {
25009        editor.newline(&Newline, window, cx);
25010    });
25011    cx.run_until_parked();
25012    cx.assert_editor_state(indoc! {"
25013        case \"$1\" in
25014            start)
25015                ;;
25016            *)
25017                ˇ
25018    "});
25019
25020    // test correct indent after newline after function opening brace
25021    cx.set_state(indoc! {"
25022        function test() {ˇ}
25023    "});
25024    cx.update_editor(|editor, window, cx| {
25025        editor.newline(&Newline, window, cx);
25026    });
25027    cx.run_until_parked();
25028    cx.assert_editor_state(indoc! {"
25029        function test() {
25030            ˇ
25031        }
25032    "});
25033
25034    // test no extra indent after semicolon on same line
25035    cx.set_state(indoc! {"
25036        echo \"test\"25037    "});
25038    cx.update_editor(|editor, window, cx| {
25039        editor.newline(&Newline, window, cx);
25040    });
25041    cx.run_until_parked();
25042    cx.assert_editor_state(indoc! {"
25043        echo \"test\";
25044        ˇ
25045    "});
25046}
25047
25048fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
25049    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
25050    point..point
25051}
25052
25053#[track_caller]
25054fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
25055    let (text, ranges) = marked_text_ranges(marked_text, true);
25056    assert_eq!(editor.text(cx), text);
25057    assert_eq!(
25058        editor.selections.ranges(cx),
25059        ranges,
25060        "Assert selections are {}",
25061        marked_text
25062    );
25063}
25064
25065pub fn handle_signature_help_request(
25066    cx: &mut EditorLspTestContext,
25067    mocked_response: lsp::SignatureHelp,
25068) -> impl Future<Output = ()> + use<> {
25069    let mut request =
25070        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
25071            let mocked_response = mocked_response.clone();
25072            async move { Ok(Some(mocked_response)) }
25073        });
25074
25075    async move {
25076        request.next().await;
25077    }
25078}
25079
25080#[track_caller]
25081pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
25082    cx.update_editor(|editor, _, _| {
25083        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
25084            let entries = menu.entries.borrow();
25085            let entries = entries
25086                .iter()
25087                .map(|entry| entry.string.as_str())
25088                .collect::<Vec<_>>();
25089            assert_eq!(entries, expected);
25090        } else {
25091            panic!("Expected completions menu");
25092        }
25093    });
25094}
25095
25096/// Handle completion request passing a marked string specifying where the completion
25097/// should be triggered from using '|' character, what range should be replaced, and what completions
25098/// should be returned using '<' and '>' to delimit the range.
25099///
25100/// Also see `handle_completion_request_with_insert_and_replace`.
25101#[track_caller]
25102pub fn handle_completion_request(
25103    marked_string: &str,
25104    completions: Vec<&'static str>,
25105    is_incomplete: bool,
25106    counter: Arc<AtomicUsize>,
25107    cx: &mut EditorLspTestContext,
25108) -> impl Future<Output = ()> {
25109    let complete_from_marker: TextRangeMarker = '|'.into();
25110    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25111    let (_, mut marked_ranges) = marked_text_ranges_by(
25112        marked_string,
25113        vec![complete_from_marker.clone(), replace_range_marker.clone()],
25114    );
25115
25116    let complete_from_position =
25117        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25118    let replace_range =
25119        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25120
25121    let mut request =
25122        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25123            let completions = completions.clone();
25124            counter.fetch_add(1, atomic::Ordering::Release);
25125            async move {
25126                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25127                assert_eq!(
25128                    params.text_document_position.position,
25129                    complete_from_position
25130                );
25131                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
25132                    is_incomplete,
25133                    item_defaults: None,
25134                    items: completions
25135                        .iter()
25136                        .map(|completion_text| lsp::CompletionItem {
25137                            label: completion_text.to_string(),
25138                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
25139                                range: replace_range,
25140                                new_text: completion_text.to_string(),
25141                            })),
25142                            ..Default::default()
25143                        })
25144                        .collect(),
25145                })))
25146            }
25147        });
25148
25149    async move {
25150        request.next().await;
25151    }
25152}
25153
25154/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
25155/// given instead, which also contains an `insert` range.
25156///
25157/// This function uses markers to define ranges:
25158/// - `|` marks the cursor position
25159/// - `<>` marks the replace range
25160/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
25161pub fn handle_completion_request_with_insert_and_replace(
25162    cx: &mut EditorLspTestContext,
25163    marked_string: &str,
25164    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
25165    counter: Arc<AtomicUsize>,
25166) -> impl Future<Output = ()> {
25167    let complete_from_marker: TextRangeMarker = '|'.into();
25168    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25169    let insert_range_marker: TextRangeMarker = ('{', '}').into();
25170
25171    let (_, mut marked_ranges) = marked_text_ranges_by(
25172        marked_string,
25173        vec![
25174            complete_from_marker.clone(),
25175            replace_range_marker.clone(),
25176            insert_range_marker.clone(),
25177        ],
25178    );
25179
25180    let complete_from_position =
25181        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25182    let replace_range =
25183        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25184
25185    let insert_range = match marked_ranges.remove(&insert_range_marker) {
25186        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
25187        _ => lsp::Range {
25188            start: replace_range.start,
25189            end: complete_from_position,
25190        },
25191    };
25192
25193    let mut request =
25194        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25195            let completions = completions.clone();
25196            counter.fetch_add(1, atomic::Ordering::Release);
25197            async move {
25198                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25199                assert_eq!(
25200                    params.text_document_position.position, complete_from_position,
25201                    "marker `|` position doesn't match",
25202                );
25203                Ok(Some(lsp::CompletionResponse::Array(
25204                    completions
25205                        .iter()
25206                        .map(|(label, new_text)| lsp::CompletionItem {
25207                            label: label.to_string(),
25208                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25209                                lsp::InsertReplaceEdit {
25210                                    insert: insert_range,
25211                                    replace: replace_range,
25212                                    new_text: new_text.to_string(),
25213                                },
25214                            )),
25215                            ..Default::default()
25216                        })
25217                        .collect(),
25218                )))
25219            }
25220        });
25221
25222    async move {
25223        request.next().await;
25224    }
25225}
25226
25227fn handle_resolve_completion_request(
25228    cx: &mut EditorLspTestContext,
25229    edits: Option<Vec<(&'static str, &'static str)>>,
25230) -> impl Future<Output = ()> {
25231    let edits = edits.map(|edits| {
25232        edits
25233            .iter()
25234            .map(|(marked_string, new_text)| {
25235                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
25236                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
25237                lsp::TextEdit::new(replace_range, new_text.to_string())
25238            })
25239            .collect::<Vec<_>>()
25240    });
25241
25242    let mut request =
25243        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
25244            let edits = edits.clone();
25245            async move {
25246                Ok(lsp::CompletionItem {
25247                    additional_text_edits: edits,
25248                    ..Default::default()
25249                })
25250            }
25251        });
25252
25253    async move {
25254        request.next().await;
25255    }
25256}
25257
25258pub(crate) fn update_test_language_settings(
25259    cx: &mut TestAppContext,
25260    f: impl Fn(&mut AllLanguageSettingsContent),
25261) {
25262    cx.update(|cx| {
25263        SettingsStore::update_global(cx, |store, cx| {
25264            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
25265        });
25266    });
25267}
25268
25269pub(crate) fn update_test_project_settings(
25270    cx: &mut TestAppContext,
25271    f: impl Fn(&mut ProjectSettingsContent),
25272) {
25273    cx.update(|cx| {
25274        SettingsStore::update_global(cx, |store, cx| {
25275            store.update_user_settings(cx, |settings| f(&mut settings.project));
25276        });
25277    });
25278}
25279
25280pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
25281    cx.update(|cx| {
25282        assets::Assets.load_test_fonts(cx);
25283        let store = SettingsStore::test(cx);
25284        cx.set_global(store);
25285        theme::init(theme::LoadThemes::JustBase, cx);
25286        release_channel::init(SemanticVersion::default(), cx);
25287        client::init_settings(cx);
25288        language::init(cx);
25289        Project::init_settings(cx);
25290        workspace::init_settings(cx);
25291        crate::init(cx);
25292    });
25293    zlog::init_test();
25294    update_test_language_settings(cx, f);
25295}
25296
25297#[track_caller]
25298fn assert_hunk_revert(
25299    not_reverted_text_with_selections: &str,
25300    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
25301    expected_reverted_text_with_selections: &str,
25302    base_text: &str,
25303    cx: &mut EditorLspTestContext,
25304) {
25305    cx.set_state(not_reverted_text_with_selections);
25306    cx.set_head_text(base_text);
25307    cx.executor().run_until_parked();
25308
25309    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
25310        let snapshot = editor.snapshot(window, cx);
25311        let reverted_hunk_statuses = snapshot
25312            .buffer_snapshot
25313            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
25314            .map(|hunk| hunk.status().kind)
25315            .collect::<Vec<_>>();
25316
25317        editor.git_restore(&Default::default(), window, cx);
25318        reverted_hunk_statuses
25319    });
25320    cx.executor().run_until_parked();
25321    cx.assert_editor_state(expected_reverted_text_with_selections);
25322    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
25323}
25324
25325#[gpui::test(iterations = 10)]
25326async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
25327    init_test(cx, |_| {});
25328
25329    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
25330    let counter = diagnostic_requests.clone();
25331
25332    let fs = FakeFs::new(cx.executor());
25333    fs.insert_tree(
25334        path!("/a"),
25335        json!({
25336            "first.rs": "fn main() { let a = 5; }",
25337            "second.rs": "// Test file",
25338        }),
25339    )
25340    .await;
25341
25342    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25343    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25344    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25345
25346    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25347    language_registry.add(rust_lang());
25348    let mut fake_servers = language_registry.register_fake_lsp(
25349        "Rust",
25350        FakeLspAdapter {
25351            capabilities: lsp::ServerCapabilities {
25352                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
25353                    lsp::DiagnosticOptions {
25354                        identifier: None,
25355                        inter_file_dependencies: true,
25356                        workspace_diagnostics: true,
25357                        work_done_progress_options: Default::default(),
25358                    },
25359                )),
25360                ..Default::default()
25361            },
25362            ..Default::default()
25363        },
25364    );
25365
25366    let editor = workspace
25367        .update(cx, |workspace, window, cx| {
25368            workspace.open_abs_path(
25369                PathBuf::from(path!("/a/first.rs")),
25370                OpenOptions::default(),
25371                window,
25372                cx,
25373            )
25374        })
25375        .unwrap()
25376        .await
25377        .unwrap()
25378        .downcast::<Editor>()
25379        .unwrap();
25380    let fake_server = fake_servers.next().await.unwrap();
25381    let server_id = fake_server.server.server_id();
25382    let mut first_request = fake_server
25383        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
25384            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
25385            let result_id = Some(new_result_id.to_string());
25386            assert_eq!(
25387                params.text_document.uri,
25388                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25389            );
25390            async move {
25391                Ok(lsp::DocumentDiagnosticReportResult::Report(
25392                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
25393                        related_documents: None,
25394                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
25395                            items: Vec::new(),
25396                            result_id,
25397                        },
25398                    }),
25399                ))
25400            }
25401        });
25402
25403    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
25404        project.update(cx, |project, cx| {
25405            let buffer_id = editor
25406                .read(cx)
25407                .buffer()
25408                .read(cx)
25409                .as_singleton()
25410                .expect("created a singleton buffer")
25411                .read(cx)
25412                .remote_id();
25413            let buffer_result_id = project
25414                .lsp_store()
25415                .read(cx)
25416                .result_id(server_id, buffer_id, cx);
25417            assert_eq!(expected, buffer_result_id);
25418        });
25419    };
25420
25421    ensure_result_id(None, cx);
25422    cx.executor().advance_clock(Duration::from_millis(60));
25423    cx.executor().run_until_parked();
25424    assert_eq!(
25425        diagnostic_requests.load(atomic::Ordering::Acquire),
25426        1,
25427        "Opening file should trigger diagnostic request"
25428    );
25429    first_request
25430        .next()
25431        .await
25432        .expect("should have sent the first diagnostics pull request");
25433    ensure_result_id(Some("1".to_string()), cx);
25434
25435    // Editing should trigger diagnostics
25436    editor.update_in(cx, |editor, window, cx| {
25437        editor.handle_input("2", window, cx)
25438    });
25439    cx.executor().advance_clock(Duration::from_millis(60));
25440    cx.executor().run_until_parked();
25441    assert_eq!(
25442        diagnostic_requests.load(atomic::Ordering::Acquire),
25443        2,
25444        "Editing should trigger diagnostic request"
25445    );
25446    ensure_result_id(Some("2".to_string()), cx);
25447
25448    // Moving cursor should not trigger diagnostic request
25449    editor.update_in(cx, |editor, window, cx| {
25450        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25451            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
25452        });
25453    });
25454    cx.executor().advance_clock(Duration::from_millis(60));
25455    cx.executor().run_until_parked();
25456    assert_eq!(
25457        diagnostic_requests.load(atomic::Ordering::Acquire),
25458        2,
25459        "Cursor movement should not trigger diagnostic request"
25460    );
25461    ensure_result_id(Some("2".to_string()), cx);
25462    // Multiple rapid edits should be debounced
25463    for _ in 0..5 {
25464        editor.update_in(cx, |editor, window, cx| {
25465            editor.handle_input("x", window, cx)
25466        });
25467    }
25468    cx.executor().advance_clock(Duration::from_millis(60));
25469    cx.executor().run_until_parked();
25470
25471    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
25472    assert!(
25473        final_requests <= 4,
25474        "Multiple rapid edits should be debounced (got {final_requests} requests)",
25475    );
25476    ensure_result_id(Some(final_requests.to_string()), cx);
25477}
25478
25479#[gpui::test]
25480async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
25481    // Regression test for issue #11671
25482    // Previously, adding a cursor after moving multiple cursors would reset
25483    // the cursor count instead of adding to the existing cursors.
25484    init_test(cx, |_| {});
25485    let mut cx = EditorTestContext::new(cx).await;
25486
25487    // Create a simple buffer with cursor at start
25488    cx.set_state(indoc! {"
25489        ˇaaaa
25490        bbbb
25491        cccc
25492        dddd
25493        eeee
25494        ffff
25495        gggg
25496        hhhh"});
25497
25498    // Add 2 cursors below (so we have 3 total)
25499    cx.update_editor(|editor, window, cx| {
25500        editor.add_selection_below(&Default::default(), window, cx);
25501        editor.add_selection_below(&Default::default(), window, cx);
25502    });
25503
25504    // Verify we have 3 cursors
25505    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
25506    assert_eq!(
25507        initial_count, 3,
25508        "Should have 3 cursors after adding 2 below"
25509    );
25510
25511    // Move down one line
25512    cx.update_editor(|editor, window, cx| {
25513        editor.move_down(&MoveDown, window, cx);
25514    });
25515
25516    // Add another cursor below
25517    cx.update_editor(|editor, window, cx| {
25518        editor.add_selection_below(&Default::default(), window, cx);
25519    });
25520
25521    // Should now have 4 cursors (3 original + 1 new)
25522    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
25523    assert_eq!(
25524        final_count, 4,
25525        "Should have 4 cursors after moving and adding another"
25526    );
25527}
25528
25529#[gpui::test(iterations = 10)]
25530async fn test_document_colors(cx: &mut TestAppContext) {
25531    let expected_color = Rgba {
25532        r: 0.33,
25533        g: 0.33,
25534        b: 0.33,
25535        a: 0.33,
25536    };
25537
25538    init_test(cx, |_| {});
25539
25540    let fs = FakeFs::new(cx.executor());
25541    fs.insert_tree(
25542        path!("/a"),
25543        json!({
25544            "first.rs": "fn main() { let a = 5; }",
25545        }),
25546    )
25547    .await;
25548
25549    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25550    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25551    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25552
25553    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25554    language_registry.add(rust_lang());
25555    let mut fake_servers = language_registry.register_fake_lsp(
25556        "Rust",
25557        FakeLspAdapter {
25558            capabilities: lsp::ServerCapabilities {
25559                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
25560                ..lsp::ServerCapabilities::default()
25561            },
25562            name: "rust-analyzer",
25563            ..FakeLspAdapter::default()
25564        },
25565    );
25566    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
25567        "Rust",
25568        FakeLspAdapter {
25569            capabilities: lsp::ServerCapabilities {
25570                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
25571                ..lsp::ServerCapabilities::default()
25572            },
25573            name: "not-rust-analyzer",
25574            ..FakeLspAdapter::default()
25575        },
25576    );
25577
25578    let editor = workspace
25579        .update(cx, |workspace, window, cx| {
25580            workspace.open_abs_path(
25581                PathBuf::from(path!("/a/first.rs")),
25582                OpenOptions::default(),
25583                window,
25584                cx,
25585            )
25586        })
25587        .unwrap()
25588        .await
25589        .unwrap()
25590        .downcast::<Editor>()
25591        .unwrap();
25592    let fake_language_server = fake_servers.next().await.unwrap();
25593    let fake_language_server_without_capabilities =
25594        fake_servers_without_capabilities.next().await.unwrap();
25595    let requests_made = Arc::new(AtomicUsize::new(0));
25596    let closure_requests_made = Arc::clone(&requests_made);
25597    let mut color_request_handle = fake_language_server
25598        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25599            let requests_made = Arc::clone(&closure_requests_made);
25600            async move {
25601                assert_eq!(
25602                    params.text_document.uri,
25603                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25604                );
25605                requests_made.fetch_add(1, atomic::Ordering::Release);
25606                Ok(vec![
25607                    lsp::ColorInformation {
25608                        range: lsp::Range {
25609                            start: lsp::Position {
25610                                line: 0,
25611                                character: 0,
25612                            },
25613                            end: lsp::Position {
25614                                line: 0,
25615                                character: 1,
25616                            },
25617                        },
25618                        color: lsp::Color {
25619                            red: 0.33,
25620                            green: 0.33,
25621                            blue: 0.33,
25622                            alpha: 0.33,
25623                        },
25624                    },
25625                    lsp::ColorInformation {
25626                        range: lsp::Range {
25627                            start: lsp::Position {
25628                                line: 0,
25629                                character: 0,
25630                            },
25631                            end: lsp::Position {
25632                                line: 0,
25633                                character: 1,
25634                            },
25635                        },
25636                        color: lsp::Color {
25637                            red: 0.33,
25638                            green: 0.33,
25639                            blue: 0.33,
25640                            alpha: 0.33,
25641                        },
25642                    },
25643                ])
25644            }
25645        });
25646
25647    let _handle = fake_language_server_without_capabilities
25648        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
25649            panic!("Should not be called");
25650        });
25651    cx.executor().advance_clock(Duration::from_millis(100));
25652    color_request_handle.next().await.unwrap();
25653    cx.run_until_parked();
25654    assert_eq!(
25655        1,
25656        requests_made.load(atomic::Ordering::Acquire),
25657        "Should query for colors once per editor open"
25658    );
25659    editor.update_in(cx, |editor, _, cx| {
25660        assert_eq!(
25661            vec![expected_color],
25662            extract_color_inlays(editor, cx),
25663            "Should have an initial inlay"
25664        );
25665    });
25666
25667    // opening another file in a split should not influence the LSP query counter
25668    workspace
25669        .update(cx, |workspace, window, cx| {
25670            assert_eq!(
25671                workspace.panes().len(),
25672                1,
25673                "Should have one pane with one editor"
25674            );
25675            workspace.move_item_to_pane_in_direction(
25676                &MoveItemToPaneInDirection {
25677                    direction: SplitDirection::Right,
25678                    focus: false,
25679                    clone: true,
25680                },
25681                window,
25682                cx,
25683            );
25684        })
25685        .unwrap();
25686    cx.run_until_parked();
25687    workspace
25688        .update(cx, |workspace, _, cx| {
25689            let panes = workspace.panes();
25690            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
25691            for pane in panes {
25692                let editor = pane
25693                    .read(cx)
25694                    .active_item()
25695                    .and_then(|item| item.downcast::<Editor>())
25696                    .expect("Should have opened an editor in each split");
25697                let editor_file = editor
25698                    .read(cx)
25699                    .buffer()
25700                    .read(cx)
25701                    .as_singleton()
25702                    .expect("test deals with singleton buffers")
25703                    .read(cx)
25704                    .file()
25705                    .expect("test buffese should have a file")
25706                    .path();
25707                assert_eq!(
25708                    editor_file.as_ref(),
25709                    rel_path("first.rs"),
25710                    "Both editors should be opened for the same file"
25711                )
25712            }
25713        })
25714        .unwrap();
25715
25716    cx.executor().advance_clock(Duration::from_millis(500));
25717    let save = editor.update_in(cx, |editor, window, cx| {
25718        editor.move_to_end(&MoveToEnd, window, cx);
25719        editor.handle_input("dirty", window, cx);
25720        editor.save(
25721            SaveOptions {
25722                format: true,
25723                autosave: true,
25724            },
25725            project.clone(),
25726            window,
25727            cx,
25728        )
25729    });
25730    save.await.unwrap();
25731
25732    color_request_handle.next().await.unwrap();
25733    cx.run_until_parked();
25734    assert_eq!(
25735        3,
25736        requests_made.load(atomic::Ordering::Acquire),
25737        "Should query for colors once per save and once per formatting after save"
25738    );
25739
25740    drop(editor);
25741    let close = workspace
25742        .update(cx, |workspace, window, cx| {
25743            workspace.active_pane().update(cx, |pane, cx| {
25744                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25745            })
25746        })
25747        .unwrap();
25748    close.await.unwrap();
25749    let close = workspace
25750        .update(cx, |workspace, window, cx| {
25751            workspace.active_pane().update(cx, |pane, cx| {
25752                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25753            })
25754        })
25755        .unwrap();
25756    close.await.unwrap();
25757    assert_eq!(
25758        3,
25759        requests_made.load(atomic::Ordering::Acquire),
25760        "After saving and closing all editors, no extra requests should be made"
25761    );
25762    workspace
25763        .update(cx, |workspace, _, cx| {
25764            assert!(
25765                workspace.active_item(cx).is_none(),
25766                "Should close all editors"
25767            )
25768        })
25769        .unwrap();
25770
25771    workspace
25772        .update(cx, |workspace, window, cx| {
25773            workspace.active_pane().update(cx, |pane, cx| {
25774                pane.navigate_backward(&workspace::GoBack, window, cx);
25775            })
25776        })
25777        .unwrap();
25778    cx.executor().advance_clock(Duration::from_millis(100));
25779    cx.run_until_parked();
25780    let editor = workspace
25781        .update(cx, |workspace, _, cx| {
25782            workspace
25783                .active_item(cx)
25784                .expect("Should have reopened the editor again after navigating back")
25785                .downcast::<Editor>()
25786                .expect("Should be an editor")
25787        })
25788        .unwrap();
25789    color_request_handle.next().await.unwrap();
25790    assert_eq!(
25791        3,
25792        requests_made.load(atomic::Ordering::Acquire),
25793        "Cache should be reused on buffer close and reopen"
25794    );
25795    editor.update(cx, |editor, cx| {
25796        assert_eq!(
25797            vec![expected_color],
25798            extract_color_inlays(editor, cx),
25799            "Should have an initial inlay"
25800        );
25801    });
25802
25803    drop(color_request_handle);
25804    let closure_requests_made = Arc::clone(&requests_made);
25805    let mut empty_color_request_handle = fake_language_server
25806        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25807            let requests_made = Arc::clone(&closure_requests_made);
25808            async move {
25809                assert_eq!(
25810                    params.text_document.uri,
25811                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25812                );
25813                requests_made.fetch_add(1, atomic::Ordering::Release);
25814                Ok(Vec::new())
25815            }
25816        });
25817    let save = editor.update_in(cx, |editor, window, cx| {
25818        editor.move_to_end(&MoveToEnd, window, cx);
25819        editor.handle_input("dirty_again", window, cx);
25820        editor.save(
25821            SaveOptions {
25822                format: false,
25823                autosave: true,
25824            },
25825            project.clone(),
25826            window,
25827            cx,
25828        )
25829    });
25830    save.await.unwrap();
25831
25832    empty_color_request_handle.next().await.unwrap();
25833    cx.run_until_parked();
25834    assert_eq!(
25835        4,
25836        requests_made.load(atomic::Ordering::Acquire),
25837        "Should query for colors once per save only, as formatting was not requested"
25838    );
25839    editor.update(cx, |editor, cx| {
25840        assert_eq!(
25841            Vec::<Rgba>::new(),
25842            extract_color_inlays(editor, cx),
25843            "Should clear all colors when the server returns an empty response"
25844        );
25845    });
25846}
25847
25848#[gpui::test]
25849async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
25850    init_test(cx, |_| {});
25851    let (editor, cx) = cx.add_window_view(Editor::single_line);
25852    editor.update_in(cx, |editor, window, cx| {
25853        editor.set_text("oops\n\nwow\n", window, cx)
25854    });
25855    cx.run_until_parked();
25856    editor.update(cx, |editor, cx| {
25857        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
25858    });
25859    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
25860    cx.run_until_parked();
25861    editor.update(cx, |editor, cx| {
25862        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
25863    });
25864}
25865
25866#[gpui::test]
25867async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
25868    init_test(cx, |_| {});
25869
25870    cx.update(|cx| {
25871        register_project_item::<Editor>(cx);
25872    });
25873
25874    let fs = FakeFs::new(cx.executor());
25875    fs.insert_tree("/root1", json!({})).await;
25876    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
25877        .await;
25878
25879    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
25880    let (workspace, cx) =
25881        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25882
25883    let worktree_id = project.update(cx, |project, cx| {
25884        project.worktrees(cx).next().unwrap().read(cx).id()
25885    });
25886
25887    let handle = workspace
25888        .update_in(cx, |workspace, window, cx| {
25889            let project_path = (worktree_id, rel_path("one.pdf"));
25890            workspace.open_path(project_path, None, true, window, cx)
25891        })
25892        .await
25893        .unwrap();
25894
25895    assert_eq!(
25896        handle.to_any().entity_type(),
25897        TypeId::of::<InvalidBufferView>()
25898    );
25899}
25900
25901#[gpui::test]
25902async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
25903    init_test(cx, |_| {});
25904
25905    let language = Arc::new(Language::new(
25906        LanguageConfig::default(),
25907        Some(tree_sitter_rust::LANGUAGE.into()),
25908    ));
25909
25910    // Test hierarchical sibling navigation
25911    let text = r#"
25912        fn outer() {
25913            if condition {
25914                let a = 1;
25915            }
25916            let b = 2;
25917        }
25918
25919        fn another() {
25920            let c = 3;
25921        }
25922    "#;
25923
25924    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
25925    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25926    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
25927
25928    // Wait for parsing to complete
25929    editor
25930        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
25931        .await;
25932
25933    editor.update_in(cx, |editor, window, cx| {
25934        // Start by selecting "let a = 1;" inside the if block
25935        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25936            s.select_display_ranges([
25937                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
25938            ]);
25939        });
25940
25941        let initial_selection = editor.selections.display_ranges(cx);
25942        assert_eq!(initial_selection.len(), 1, "Should have one selection");
25943
25944        // Test select next sibling - should move up levels to find the next sibling
25945        // Since "let a = 1;" has no siblings in the if block, it should move up
25946        // to find "let b = 2;" which is a sibling of the if block
25947        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
25948        let next_selection = editor.selections.display_ranges(cx);
25949
25950        // Should have a selection and it should be different from the initial
25951        assert_eq!(
25952            next_selection.len(),
25953            1,
25954            "Should have one selection after next"
25955        );
25956        assert_ne!(
25957            next_selection[0], initial_selection[0],
25958            "Next sibling selection should be different"
25959        );
25960
25961        // Test hierarchical navigation by going to the end of the current function
25962        // and trying to navigate to the next function
25963        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25964            s.select_display_ranges([
25965                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
25966            ]);
25967        });
25968
25969        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
25970        let function_next_selection = editor.selections.display_ranges(cx);
25971
25972        // Should move to the next function
25973        assert_eq!(
25974            function_next_selection.len(),
25975            1,
25976            "Should have one selection after function next"
25977        );
25978
25979        // Test select previous sibling navigation
25980        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
25981        let prev_selection = editor.selections.display_ranges(cx);
25982
25983        // Should have a selection and it should be different
25984        assert_eq!(
25985            prev_selection.len(),
25986            1,
25987            "Should have one selection after prev"
25988        );
25989        assert_ne!(
25990            prev_selection[0], function_next_selection[0],
25991            "Previous sibling selection should be different from next"
25992        );
25993    });
25994}
25995
25996#[gpui::test]
25997async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
25998    init_test(cx, |_| {});
25999
26000    let mut cx = EditorTestContext::new(cx).await;
26001    cx.set_state(
26002        "let ˇvariable = 42;
26003let another = variable + 1;
26004let result = variable * 2;",
26005    );
26006
26007    // Set up document highlights manually (simulating LSP response)
26008    cx.update_editor(|editor, _window, cx| {
26009        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
26010
26011        // Create highlights for "variable" occurrences
26012        let highlight_ranges = [
26013            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
26014            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
26015            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
26016        ];
26017
26018        let anchor_ranges: Vec<_> = highlight_ranges
26019            .iter()
26020            .map(|range| range.clone().to_anchors(&buffer_snapshot))
26021            .collect();
26022
26023        editor.highlight_background::<DocumentHighlightRead>(
26024            &anchor_ranges,
26025            |theme| theme.colors().editor_document_highlight_read_background,
26026            cx,
26027        );
26028    });
26029
26030    // Go to next highlight - should move to second "variable"
26031    cx.update_editor(|editor, window, cx| {
26032        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26033    });
26034    cx.assert_editor_state(
26035        "let variable = 42;
26036let another = ˇvariable + 1;
26037let result = variable * 2;",
26038    );
26039
26040    // Go to next highlight - should move to third "variable"
26041    cx.update_editor(|editor, window, cx| {
26042        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26043    });
26044    cx.assert_editor_state(
26045        "let variable = 42;
26046let another = variable + 1;
26047let result = ˇvariable * 2;",
26048    );
26049
26050    // Go to next highlight - should stay at third "variable" (no wrap-around)
26051    cx.update_editor(|editor, window, cx| {
26052        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26053    });
26054    cx.assert_editor_state(
26055        "let variable = 42;
26056let another = variable + 1;
26057let result = ˇvariable * 2;",
26058    );
26059
26060    // Now test going backwards from third position
26061    cx.update_editor(|editor, window, cx| {
26062        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26063    });
26064    cx.assert_editor_state(
26065        "let variable = 42;
26066let another = ˇvariable + 1;
26067let result = variable * 2;",
26068    );
26069
26070    // Go to previous highlight - should move to first "variable"
26071    cx.update_editor(|editor, window, cx| {
26072        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26073    });
26074    cx.assert_editor_state(
26075        "let ˇvariable = 42;
26076let another = variable + 1;
26077let result = variable * 2;",
26078    );
26079
26080    // Go to previous highlight - should stay on first "variable"
26081    cx.update_editor(|editor, window, cx| {
26082        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26083    });
26084    cx.assert_editor_state(
26085        "let ˇvariable = 42;
26086let another = variable + 1;
26087let result = variable * 2;",
26088    );
26089}
26090
26091#[gpui::test]
26092async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
26093    cx: &mut gpui::TestAppContext,
26094) {
26095    init_test(cx, |_| {});
26096
26097    let url = "https://zed.dev";
26098
26099    let markdown_language = Arc::new(Language::new(
26100        LanguageConfig {
26101            name: "Markdown".into(),
26102            ..LanguageConfig::default()
26103        },
26104        None,
26105    ));
26106
26107    let mut cx = EditorTestContext::new(cx).await;
26108    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26109    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
26110
26111    cx.update_editor(|editor, window, cx| {
26112        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26113        editor.paste(&Paste, window, cx);
26114    });
26115
26116    cx.assert_editor_state(&format!(
26117        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
26118    ));
26119}
26120
26121#[gpui::test]
26122async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
26123    cx: &mut gpui::TestAppContext,
26124) {
26125    init_test(cx, |_| {});
26126
26127    let url = "https://zed.dev";
26128
26129    let markdown_language = Arc::new(Language::new(
26130        LanguageConfig {
26131            name: "Markdown".into(),
26132            ..LanguageConfig::default()
26133        },
26134        None,
26135    ));
26136
26137    let mut cx = EditorTestContext::new(cx).await;
26138    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26139    cx.set_state(&format!(
26140        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
26141    ));
26142
26143    cx.update_editor(|editor, window, cx| {
26144        editor.copy(&Copy, window, cx);
26145    });
26146
26147    cx.set_state(&format!(
26148        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
26149    ));
26150
26151    cx.update_editor(|editor, window, cx| {
26152        editor.paste(&Paste, window, cx);
26153    });
26154
26155    cx.assert_editor_state(&format!(
26156        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
26157    ));
26158}
26159
26160#[gpui::test]
26161async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
26162    cx: &mut gpui::TestAppContext,
26163) {
26164    init_test(cx, |_| {});
26165
26166    let url = "https://zed.dev";
26167
26168    let markdown_language = Arc::new(Language::new(
26169        LanguageConfig {
26170            name: "Markdown".into(),
26171            ..LanguageConfig::default()
26172        },
26173        None,
26174    ));
26175
26176    let mut cx = EditorTestContext::new(cx).await;
26177    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26178    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
26179
26180    cx.update_editor(|editor, window, cx| {
26181        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26182        editor.paste(&Paste, window, cx);
26183    });
26184
26185    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
26186}
26187
26188#[gpui::test]
26189async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
26190    cx: &mut gpui::TestAppContext,
26191) {
26192    init_test(cx, |_| {});
26193
26194    let text = "Awesome";
26195
26196    let markdown_language = Arc::new(Language::new(
26197        LanguageConfig {
26198            name: "Markdown".into(),
26199            ..LanguageConfig::default()
26200        },
26201        None,
26202    ));
26203
26204    let mut cx = EditorTestContext::new(cx).await;
26205    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26206    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
26207
26208    cx.update_editor(|editor, window, cx| {
26209        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
26210        editor.paste(&Paste, window, cx);
26211    });
26212
26213    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
26214}
26215
26216#[gpui::test]
26217async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
26218    cx: &mut gpui::TestAppContext,
26219) {
26220    init_test(cx, |_| {});
26221
26222    let url = "https://zed.dev";
26223
26224    let markdown_language = Arc::new(Language::new(
26225        LanguageConfig {
26226            name: "Rust".into(),
26227            ..LanguageConfig::default()
26228        },
26229        None,
26230    ));
26231
26232    let mut cx = EditorTestContext::new(cx).await;
26233    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26234    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
26235
26236    cx.update_editor(|editor, window, cx| {
26237        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26238        editor.paste(&Paste, window, cx);
26239    });
26240
26241    cx.assert_editor_state(&format!(
26242        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
26243    ));
26244}
26245
26246#[gpui::test]
26247async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
26248    cx: &mut TestAppContext,
26249) {
26250    init_test(cx, |_| {});
26251
26252    let url = "https://zed.dev";
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 (editor, cx) = cx.add_window_view(|window, cx| {
26263        let multi_buffer = MultiBuffer::build_multi(
26264            [
26265                ("this will embed -> link", vec![Point::row_range(0..1)]),
26266                ("this will replace -> link", vec![Point::row_range(0..1)]),
26267            ],
26268            cx,
26269        );
26270        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
26271        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26272            s.select_ranges(vec![
26273                Point::new(0, 19)..Point::new(0, 23),
26274                Point::new(1, 21)..Point::new(1, 25),
26275            ])
26276        });
26277        let first_buffer_id = multi_buffer
26278            .read(cx)
26279            .excerpt_buffer_ids()
26280            .into_iter()
26281            .next()
26282            .unwrap();
26283        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
26284        first_buffer.update(cx, |buffer, cx| {
26285            buffer.set_language(Some(markdown_language.clone()), cx);
26286        });
26287
26288        editor
26289    });
26290    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
26291
26292    cx.update_editor(|editor, window, cx| {
26293        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26294        editor.paste(&Paste, window, cx);
26295    });
26296
26297    cx.assert_editor_state(&format!(
26298        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
26299    ));
26300}
26301
26302#[track_caller]
26303fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
26304    editor
26305        .all_inlays(cx)
26306        .into_iter()
26307        .filter_map(|inlay| inlay.get_color())
26308        .map(Rgba::from)
26309        .collect()
26310}