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), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4247                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 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            assert_eq!(
11938                params.context.only,
11939                Some(vec!["code-action-1".into(), "code-action-2".into()])
11940            );
11941            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
11942            Ok(Some(vec![
11943                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
11944                    kind: Some("code-action-1".into()),
11945                    edit: Some(lsp::WorkspaceEdit::new(
11946                        [(
11947                            uri.clone(),
11948                            vec![lsp::TextEdit::new(
11949                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11950                                "applied-code-action-1-edit\n".to_string(),
11951                            )],
11952                        )]
11953                        .into_iter()
11954                        .collect(),
11955                    )),
11956                    command: Some(lsp::Command {
11957                        command: "the-command-for-code-action-1".into(),
11958                        ..Default::default()
11959                    }),
11960                    ..Default::default()
11961                }),
11962                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
11963                    kind: Some("code-action-2".into()),
11964                    edit: Some(lsp::WorkspaceEdit::new(
11965                        [(
11966                            uri,
11967                            vec![lsp::TextEdit::new(
11968                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11969                                "applied-code-action-2-edit\n".to_string(),
11970                            )],
11971                        )]
11972                        .into_iter()
11973                        .collect(),
11974                    )),
11975                    ..Default::default()
11976                }),
11977            ]))
11978        },
11979    );
11980
11981    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
11982        move |params, _| async move { Ok(params) }
11983    });
11984
11985    let command_lock = Arc::new(futures::lock::Mutex::new(()));
11986    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
11987        let fake = fake_server.clone();
11988        let lock = command_lock.clone();
11989        move |params, _| {
11990            assert_eq!(params.command, "the-command-for-code-action-1");
11991            let fake = fake.clone();
11992            let lock = lock.clone();
11993            async move {
11994                lock.lock().await;
11995                fake.server
11996                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
11997                        label: None,
11998                        edit: lsp::WorkspaceEdit {
11999                            changes: Some(
12000                                [(
12001                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
12002                                    vec![lsp::TextEdit {
12003                                        range: lsp::Range::new(
12004                                            lsp::Position::new(0, 0),
12005                                            lsp::Position::new(0, 0),
12006                                        ),
12007                                        new_text: "applied-code-action-1-command\n".into(),
12008                                    }],
12009                                )]
12010                                .into_iter()
12011                                .collect(),
12012                            ),
12013                            ..Default::default()
12014                        },
12015                    })
12016                    .await
12017                    .into_response()
12018                    .unwrap();
12019                Ok(Some(json!(null)))
12020            }
12021        }
12022    });
12023
12024    cx.executor().start_waiting();
12025    editor
12026        .update_in(cx, |editor, window, cx| {
12027            editor.perform_format(
12028                project.clone(),
12029                FormatTrigger::Manual,
12030                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12031                window,
12032                cx,
12033            )
12034        })
12035        .unwrap()
12036        .await;
12037    editor.update(cx, |editor, cx| {
12038        assert_eq!(
12039            editor.text(cx),
12040            r#"
12041                applied-code-action-2-edit
12042                applied-code-action-1-command
12043                applied-code-action-1-edit
12044                applied-formatting
12045                one
12046                two
12047                three
12048            "#
12049            .unindent()
12050        );
12051    });
12052
12053    editor.update_in(cx, |editor, window, cx| {
12054        editor.undo(&Default::default(), window, cx);
12055        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12056    });
12057
12058    // Perform a manual edit while waiting for an LSP command
12059    // that's being run as part of a formatting code action.
12060    let lock_guard = command_lock.lock().await;
12061    let format = editor
12062        .update_in(cx, |editor, window, cx| {
12063            editor.perform_format(
12064                project.clone(),
12065                FormatTrigger::Manual,
12066                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12067                window,
12068                cx,
12069            )
12070        })
12071        .unwrap();
12072    cx.run_until_parked();
12073    editor.update(cx, |editor, cx| {
12074        assert_eq!(
12075            editor.text(cx),
12076            r#"
12077                applied-code-action-1-edit
12078                applied-formatting
12079                one
12080                two
12081                three
12082            "#
12083            .unindent()
12084        );
12085
12086        editor.buffer.update(cx, |buffer, cx| {
12087            let ix = buffer.len(cx);
12088            buffer.edit([(ix..ix, "edited\n")], None, cx);
12089        });
12090    });
12091
12092    // Allow the LSP command to proceed. Because the buffer was edited,
12093    // the second code action will not be run.
12094    drop(lock_guard);
12095    format.await;
12096    editor.update_in(cx, |editor, window, cx| {
12097        assert_eq!(
12098            editor.text(cx),
12099            r#"
12100                applied-code-action-1-command
12101                applied-code-action-1-edit
12102                applied-formatting
12103                one
12104                two
12105                three
12106                edited
12107            "#
12108            .unindent()
12109        );
12110
12111        // The manual edit is undone first, because it is the last thing the user did
12112        // (even though the command completed afterwards).
12113        editor.undo(&Default::default(), window, cx);
12114        assert_eq!(
12115            editor.text(cx),
12116            r#"
12117                applied-code-action-1-command
12118                applied-code-action-1-edit
12119                applied-formatting
12120                one
12121                two
12122                three
12123            "#
12124            .unindent()
12125        );
12126
12127        // All the formatting (including the command, which completed after the manual edit)
12128        // is undone together.
12129        editor.undo(&Default::default(), window, cx);
12130        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12131    });
12132}
12133
12134#[gpui::test]
12135async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12136    init_test(cx, |settings| {
12137        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
12138            Formatter::LanguageServer { name: None },
12139        ])))
12140    });
12141
12142    let fs = FakeFs::new(cx.executor());
12143    fs.insert_file(path!("/file.ts"), Default::default()).await;
12144
12145    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12146
12147    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12148    language_registry.add(Arc::new(Language::new(
12149        LanguageConfig {
12150            name: "TypeScript".into(),
12151            matcher: LanguageMatcher {
12152                path_suffixes: vec!["ts".to_string()],
12153                ..Default::default()
12154            },
12155            ..LanguageConfig::default()
12156        },
12157        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12158    )));
12159    update_test_language_settings(cx, |settings| {
12160        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12161    });
12162    let mut fake_servers = language_registry.register_fake_lsp(
12163        "TypeScript",
12164        FakeLspAdapter {
12165            capabilities: lsp::ServerCapabilities {
12166                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12167                ..Default::default()
12168            },
12169            ..Default::default()
12170        },
12171    );
12172
12173    let buffer = project
12174        .update(cx, |project, cx| {
12175            project.open_local_buffer(path!("/file.ts"), cx)
12176        })
12177        .await
12178        .unwrap();
12179
12180    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12181    let (editor, cx) = cx.add_window_view(|window, cx| {
12182        build_editor_with_project(project.clone(), buffer, window, cx)
12183    });
12184    editor.update_in(cx, |editor, window, cx| {
12185        editor.set_text(
12186            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12187            window,
12188            cx,
12189        )
12190    });
12191
12192    cx.executor().start_waiting();
12193    let fake_server = fake_servers.next().await.unwrap();
12194
12195    let format = editor
12196        .update_in(cx, |editor, window, cx| {
12197            editor.perform_code_action_kind(
12198                project.clone(),
12199                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12200                window,
12201                cx,
12202            )
12203        })
12204        .unwrap();
12205    fake_server
12206        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12207            assert_eq!(
12208                params.text_document.uri,
12209                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12210            );
12211            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12212                lsp::CodeAction {
12213                    title: "Organize Imports".to_string(),
12214                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12215                    edit: Some(lsp::WorkspaceEdit {
12216                        changes: Some(
12217                            [(
12218                                params.text_document.uri.clone(),
12219                                vec![lsp::TextEdit::new(
12220                                    lsp::Range::new(
12221                                        lsp::Position::new(1, 0),
12222                                        lsp::Position::new(2, 0),
12223                                    ),
12224                                    "".to_string(),
12225                                )],
12226                            )]
12227                            .into_iter()
12228                            .collect(),
12229                        ),
12230                        ..Default::default()
12231                    }),
12232                    ..Default::default()
12233                },
12234            )]))
12235        })
12236        .next()
12237        .await;
12238    cx.executor().start_waiting();
12239    format.await;
12240    assert_eq!(
12241        editor.update(cx, |editor, cx| editor.text(cx)),
12242        "import { a } from 'module';\n\nconst x = a;\n"
12243    );
12244
12245    editor.update_in(cx, |editor, window, cx| {
12246        editor.set_text(
12247            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12248            window,
12249            cx,
12250        )
12251    });
12252    // Ensure we don't lock if code action hangs.
12253    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12254        move |params, _| async move {
12255            assert_eq!(
12256                params.text_document.uri,
12257                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12258            );
12259            futures::future::pending::<()>().await;
12260            unreachable!()
12261        },
12262    );
12263    let format = editor
12264        .update_in(cx, |editor, window, cx| {
12265            editor.perform_code_action_kind(
12266                project,
12267                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12268                window,
12269                cx,
12270            )
12271        })
12272        .unwrap();
12273    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
12274    cx.executor().start_waiting();
12275    format.await;
12276    assert_eq!(
12277        editor.update(cx, |editor, cx| editor.text(cx)),
12278        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
12279    );
12280}
12281
12282#[gpui::test]
12283async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
12284    init_test(cx, |_| {});
12285
12286    let mut cx = EditorLspTestContext::new_rust(
12287        lsp::ServerCapabilities {
12288            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12289            ..Default::default()
12290        },
12291        cx,
12292    )
12293    .await;
12294
12295    cx.set_state(indoc! {"
12296        one.twoˇ
12297    "});
12298
12299    // The format request takes a long time. When it completes, it inserts
12300    // a newline and an indent before the `.`
12301    cx.lsp
12302        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12303            let executor = cx.background_executor().clone();
12304            async move {
12305                executor.timer(Duration::from_millis(100)).await;
12306                Ok(Some(vec![lsp::TextEdit {
12307                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12308                    new_text: "\n    ".into(),
12309                }]))
12310            }
12311        });
12312
12313    // Submit a format request.
12314    let format_1 = cx
12315        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12316        .unwrap();
12317    cx.executor().run_until_parked();
12318
12319    // Submit a second format request.
12320    let format_2 = cx
12321        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12322        .unwrap();
12323    cx.executor().run_until_parked();
12324
12325    // Wait for both format requests to complete
12326    cx.executor().advance_clock(Duration::from_millis(200));
12327    cx.executor().start_waiting();
12328    format_1.await.unwrap();
12329    cx.executor().start_waiting();
12330    format_2.await.unwrap();
12331
12332    // The formatting edits only happens once.
12333    cx.assert_editor_state(indoc! {"
12334        one
12335            .twoˇ
12336    "});
12337}
12338
12339#[gpui::test]
12340async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12341    init_test(cx, |settings| {
12342        settings.defaults.formatter = Some(SelectedFormatter::Auto)
12343    });
12344
12345    let mut cx = EditorLspTestContext::new_rust(
12346        lsp::ServerCapabilities {
12347            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12348            ..Default::default()
12349        },
12350        cx,
12351    )
12352    .await;
12353
12354    // Set up a buffer white some trailing whitespace and no trailing newline.
12355    cx.set_state(
12356        &[
12357            "one ",   //
12358            "twoˇ",   //
12359            "three ", //
12360            "four",   //
12361        ]
12362        .join("\n"),
12363    );
12364
12365    // Submit a format request.
12366    let format = cx
12367        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12368        .unwrap();
12369
12370    // Record which buffer changes have been sent to the language server
12371    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12372    cx.lsp
12373        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12374            let buffer_changes = buffer_changes.clone();
12375            move |params, _| {
12376                buffer_changes.lock().extend(
12377                    params
12378                        .content_changes
12379                        .into_iter()
12380                        .map(|e| (e.range.unwrap(), e.text)),
12381                );
12382            }
12383        });
12384
12385    // Handle formatting requests to the language server.
12386    cx.lsp
12387        .set_request_handler::<lsp::request::Formatting, _, _>({
12388            let buffer_changes = buffer_changes.clone();
12389            move |_, _| {
12390                // When formatting is requested, trailing whitespace has already been stripped,
12391                // and the trailing newline has already been added.
12392                assert_eq!(
12393                    &buffer_changes.lock()[1..],
12394                    &[
12395                        (
12396                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12397                            "".into()
12398                        ),
12399                        (
12400                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12401                            "".into()
12402                        ),
12403                        (
12404                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12405                            "\n".into()
12406                        ),
12407                    ]
12408                );
12409
12410                // Insert blank lines between each line of the buffer.
12411                async move {
12412                    Ok(Some(vec![
12413                        lsp::TextEdit {
12414                            range: lsp::Range::new(
12415                                lsp::Position::new(1, 0),
12416                                lsp::Position::new(1, 0),
12417                            ),
12418                            new_text: "\n".into(),
12419                        },
12420                        lsp::TextEdit {
12421                            range: lsp::Range::new(
12422                                lsp::Position::new(2, 0),
12423                                lsp::Position::new(2, 0),
12424                            ),
12425                            new_text: "\n".into(),
12426                        },
12427                    ]))
12428                }
12429            }
12430        });
12431
12432    // After formatting the buffer, the trailing whitespace is stripped,
12433    // a newline is appended, and the edits provided by the language server
12434    // have been applied.
12435    format.await.unwrap();
12436    cx.assert_editor_state(
12437        &[
12438            "one",   //
12439            "",      //
12440            "twoˇ",  //
12441            "",      //
12442            "three", //
12443            "four",  //
12444            "",      //
12445        ]
12446        .join("\n"),
12447    );
12448
12449    // Undoing the formatting undoes the trailing whitespace removal, the
12450    // trailing newline, and the LSP edits.
12451    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12452    cx.assert_editor_state(
12453        &[
12454            "one ",   //
12455            "twoˇ",   //
12456            "three ", //
12457            "four",   //
12458        ]
12459        .join("\n"),
12460    );
12461}
12462
12463#[gpui::test]
12464async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12465    cx: &mut TestAppContext,
12466) {
12467    init_test(cx, |_| {});
12468
12469    cx.update(|cx| {
12470        cx.update_global::<SettingsStore, _>(|settings, cx| {
12471            settings.update_user_settings(cx, |settings| {
12472                settings.editor.auto_signature_help = Some(true);
12473            });
12474        });
12475    });
12476
12477    let mut cx = EditorLspTestContext::new_rust(
12478        lsp::ServerCapabilities {
12479            signature_help_provider: Some(lsp::SignatureHelpOptions {
12480                ..Default::default()
12481            }),
12482            ..Default::default()
12483        },
12484        cx,
12485    )
12486    .await;
12487
12488    let language = Language::new(
12489        LanguageConfig {
12490            name: "Rust".into(),
12491            brackets: BracketPairConfig {
12492                pairs: vec![
12493                    BracketPair {
12494                        start: "{".to_string(),
12495                        end: "}".to_string(),
12496                        close: true,
12497                        surround: true,
12498                        newline: true,
12499                    },
12500                    BracketPair {
12501                        start: "(".to_string(),
12502                        end: ")".to_string(),
12503                        close: true,
12504                        surround: true,
12505                        newline: true,
12506                    },
12507                    BracketPair {
12508                        start: "/*".to_string(),
12509                        end: " */".to_string(),
12510                        close: true,
12511                        surround: true,
12512                        newline: true,
12513                    },
12514                    BracketPair {
12515                        start: "[".to_string(),
12516                        end: "]".to_string(),
12517                        close: false,
12518                        surround: false,
12519                        newline: true,
12520                    },
12521                    BracketPair {
12522                        start: "\"".to_string(),
12523                        end: "\"".to_string(),
12524                        close: true,
12525                        surround: true,
12526                        newline: false,
12527                    },
12528                    BracketPair {
12529                        start: "<".to_string(),
12530                        end: ">".to_string(),
12531                        close: false,
12532                        surround: true,
12533                        newline: true,
12534                    },
12535                ],
12536                ..Default::default()
12537            },
12538            autoclose_before: "})]".to_string(),
12539            ..Default::default()
12540        },
12541        Some(tree_sitter_rust::LANGUAGE.into()),
12542    );
12543    let language = Arc::new(language);
12544
12545    cx.language_registry().add(language.clone());
12546    cx.update_buffer(|buffer, cx| {
12547        buffer.set_language(Some(language), cx);
12548    });
12549
12550    cx.set_state(
12551        &r#"
12552            fn main() {
12553                sampleˇ
12554            }
12555        "#
12556        .unindent(),
12557    );
12558
12559    cx.update_editor(|editor, window, cx| {
12560        editor.handle_input("(", window, cx);
12561    });
12562    cx.assert_editor_state(
12563        &"
12564            fn main() {
12565                sample(ˇ)
12566            }
12567        "
12568        .unindent(),
12569    );
12570
12571    let mocked_response = lsp::SignatureHelp {
12572        signatures: vec![lsp::SignatureInformation {
12573            label: "fn sample(param1: u8, param2: u8)".to_string(),
12574            documentation: None,
12575            parameters: Some(vec![
12576                lsp::ParameterInformation {
12577                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12578                    documentation: None,
12579                },
12580                lsp::ParameterInformation {
12581                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12582                    documentation: None,
12583                },
12584            ]),
12585            active_parameter: None,
12586        }],
12587        active_signature: Some(0),
12588        active_parameter: Some(0),
12589    };
12590    handle_signature_help_request(&mut cx, mocked_response).await;
12591
12592    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12593        .await;
12594
12595    cx.editor(|editor, _, _| {
12596        let signature_help_state = editor.signature_help_state.popover().cloned();
12597        let signature = signature_help_state.unwrap();
12598        assert_eq!(
12599            signature.signatures[signature.current_signature].label,
12600            "fn sample(param1: u8, param2: u8)"
12601        );
12602    });
12603}
12604
12605#[gpui::test]
12606async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
12607    init_test(cx, |_| {});
12608
12609    cx.update(|cx| {
12610        cx.update_global::<SettingsStore, _>(|settings, cx| {
12611            settings.update_user_settings(cx, |settings| {
12612                settings.editor.auto_signature_help = Some(false);
12613                settings.editor.show_signature_help_after_edits = Some(false);
12614            });
12615        });
12616    });
12617
12618    let mut cx = EditorLspTestContext::new_rust(
12619        lsp::ServerCapabilities {
12620            signature_help_provider: Some(lsp::SignatureHelpOptions {
12621                ..Default::default()
12622            }),
12623            ..Default::default()
12624        },
12625        cx,
12626    )
12627    .await;
12628
12629    let language = Language::new(
12630        LanguageConfig {
12631            name: "Rust".into(),
12632            brackets: BracketPairConfig {
12633                pairs: vec![
12634                    BracketPair {
12635                        start: "{".to_string(),
12636                        end: "}".to_string(),
12637                        close: true,
12638                        surround: true,
12639                        newline: true,
12640                    },
12641                    BracketPair {
12642                        start: "(".to_string(),
12643                        end: ")".to_string(),
12644                        close: true,
12645                        surround: true,
12646                        newline: true,
12647                    },
12648                    BracketPair {
12649                        start: "/*".to_string(),
12650                        end: " */".to_string(),
12651                        close: true,
12652                        surround: true,
12653                        newline: true,
12654                    },
12655                    BracketPair {
12656                        start: "[".to_string(),
12657                        end: "]".to_string(),
12658                        close: false,
12659                        surround: false,
12660                        newline: true,
12661                    },
12662                    BracketPair {
12663                        start: "\"".to_string(),
12664                        end: "\"".to_string(),
12665                        close: true,
12666                        surround: true,
12667                        newline: false,
12668                    },
12669                    BracketPair {
12670                        start: "<".to_string(),
12671                        end: ">".to_string(),
12672                        close: false,
12673                        surround: true,
12674                        newline: true,
12675                    },
12676                ],
12677                ..Default::default()
12678            },
12679            autoclose_before: "})]".to_string(),
12680            ..Default::default()
12681        },
12682        Some(tree_sitter_rust::LANGUAGE.into()),
12683    );
12684    let language = Arc::new(language);
12685
12686    cx.language_registry().add(language.clone());
12687    cx.update_buffer(|buffer, cx| {
12688        buffer.set_language(Some(language), cx);
12689    });
12690
12691    // Ensure that signature_help is not called when no signature help is enabled.
12692    cx.set_state(
12693        &r#"
12694            fn main() {
12695                sampleˇ
12696            }
12697        "#
12698        .unindent(),
12699    );
12700    cx.update_editor(|editor, window, cx| {
12701        editor.handle_input("(", window, cx);
12702    });
12703    cx.assert_editor_state(
12704        &"
12705            fn main() {
12706                sample(ˇ)
12707            }
12708        "
12709        .unindent(),
12710    );
12711    cx.editor(|editor, _, _| {
12712        assert!(editor.signature_help_state.task().is_none());
12713    });
12714
12715    let mocked_response = lsp::SignatureHelp {
12716        signatures: vec![lsp::SignatureInformation {
12717            label: "fn sample(param1: u8, param2: u8)".to_string(),
12718            documentation: None,
12719            parameters: Some(vec![
12720                lsp::ParameterInformation {
12721                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12722                    documentation: None,
12723                },
12724                lsp::ParameterInformation {
12725                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12726                    documentation: None,
12727                },
12728            ]),
12729            active_parameter: None,
12730        }],
12731        active_signature: Some(0),
12732        active_parameter: Some(0),
12733    };
12734
12735    // Ensure that signature_help is called when enabled afte edits
12736    cx.update(|_, cx| {
12737        cx.update_global::<SettingsStore, _>(|settings, cx| {
12738            settings.update_user_settings(cx, |settings| {
12739                settings.editor.auto_signature_help = Some(false);
12740                settings.editor.show_signature_help_after_edits = Some(true);
12741            });
12742        });
12743    });
12744    cx.set_state(
12745        &r#"
12746            fn main() {
12747                sampleˇ
12748            }
12749        "#
12750        .unindent(),
12751    );
12752    cx.update_editor(|editor, window, cx| {
12753        editor.handle_input("(", window, cx);
12754    });
12755    cx.assert_editor_state(
12756        &"
12757            fn main() {
12758                sample(ˇ)
12759            }
12760        "
12761        .unindent(),
12762    );
12763    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12764    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12765        .await;
12766    cx.update_editor(|editor, _, _| {
12767        let signature_help_state = editor.signature_help_state.popover().cloned();
12768        assert!(signature_help_state.is_some());
12769        let signature = signature_help_state.unwrap();
12770        assert_eq!(
12771            signature.signatures[signature.current_signature].label,
12772            "fn sample(param1: u8, param2: u8)"
12773        );
12774        editor.signature_help_state = SignatureHelpState::default();
12775    });
12776
12777    // Ensure that signature_help is called when auto signature help override is enabled
12778    cx.update(|_, cx| {
12779        cx.update_global::<SettingsStore, _>(|settings, cx| {
12780            settings.update_user_settings(cx, |settings| {
12781                settings.editor.auto_signature_help = Some(true);
12782                settings.editor.show_signature_help_after_edits = Some(false);
12783            });
12784        });
12785    });
12786    cx.set_state(
12787        &r#"
12788            fn main() {
12789                sampleˇ
12790            }
12791        "#
12792        .unindent(),
12793    );
12794    cx.update_editor(|editor, window, cx| {
12795        editor.handle_input("(", window, cx);
12796    });
12797    cx.assert_editor_state(
12798        &"
12799            fn main() {
12800                sample(ˇ)
12801            }
12802        "
12803        .unindent(),
12804    );
12805    handle_signature_help_request(&mut cx, mocked_response).await;
12806    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12807        .await;
12808    cx.editor(|editor, _, _| {
12809        let signature_help_state = editor.signature_help_state.popover().cloned();
12810        assert!(signature_help_state.is_some());
12811        let signature = signature_help_state.unwrap();
12812        assert_eq!(
12813            signature.signatures[signature.current_signature].label,
12814            "fn sample(param1: u8, param2: u8)"
12815        );
12816    });
12817}
12818
12819#[gpui::test]
12820async fn test_signature_help(cx: &mut TestAppContext) {
12821    init_test(cx, |_| {});
12822    cx.update(|cx| {
12823        cx.update_global::<SettingsStore, _>(|settings, cx| {
12824            settings.update_user_settings(cx, |settings| {
12825                settings.editor.auto_signature_help = Some(true);
12826            });
12827        });
12828    });
12829
12830    let mut cx = EditorLspTestContext::new_rust(
12831        lsp::ServerCapabilities {
12832            signature_help_provider: Some(lsp::SignatureHelpOptions {
12833                ..Default::default()
12834            }),
12835            ..Default::default()
12836        },
12837        cx,
12838    )
12839    .await;
12840
12841    // A test that directly calls `show_signature_help`
12842    cx.update_editor(|editor, window, cx| {
12843        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12844    });
12845
12846    let mocked_response = lsp::SignatureHelp {
12847        signatures: vec![lsp::SignatureInformation {
12848            label: "fn sample(param1: u8, param2: u8)".to_string(),
12849            documentation: None,
12850            parameters: Some(vec![
12851                lsp::ParameterInformation {
12852                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12853                    documentation: None,
12854                },
12855                lsp::ParameterInformation {
12856                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12857                    documentation: None,
12858                },
12859            ]),
12860            active_parameter: None,
12861        }],
12862        active_signature: Some(0),
12863        active_parameter: Some(0),
12864    };
12865    handle_signature_help_request(&mut cx, mocked_response).await;
12866
12867    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12868        .await;
12869
12870    cx.editor(|editor, _, _| {
12871        let signature_help_state = editor.signature_help_state.popover().cloned();
12872        assert!(signature_help_state.is_some());
12873        let signature = signature_help_state.unwrap();
12874        assert_eq!(
12875            signature.signatures[signature.current_signature].label,
12876            "fn sample(param1: u8, param2: u8)"
12877        );
12878    });
12879
12880    // When exiting outside from inside the brackets, `signature_help` is closed.
12881    cx.set_state(indoc! {"
12882        fn main() {
12883            sample(ˇ);
12884        }
12885
12886        fn sample(param1: u8, param2: u8) {}
12887    "});
12888
12889    cx.update_editor(|editor, window, cx| {
12890        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12891            s.select_ranges([0..0])
12892        });
12893    });
12894
12895    let mocked_response = lsp::SignatureHelp {
12896        signatures: Vec::new(),
12897        active_signature: None,
12898        active_parameter: None,
12899    };
12900    handle_signature_help_request(&mut cx, mocked_response).await;
12901
12902    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
12903        .await;
12904
12905    cx.editor(|editor, _, _| {
12906        assert!(!editor.signature_help_state.is_shown());
12907    });
12908
12909    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
12910    cx.set_state(indoc! {"
12911        fn main() {
12912            sample(ˇ);
12913        }
12914
12915        fn sample(param1: u8, param2: u8) {}
12916    "});
12917
12918    let mocked_response = lsp::SignatureHelp {
12919        signatures: vec![lsp::SignatureInformation {
12920            label: "fn sample(param1: u8, param2: u8)".to_string(),
12921            documentation: None,
12922            parameters: Some(vec![
12923                lsp::ParameterInformation {
12924                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12925                    documentation: None,
12926                },
12927                lsp::ParameterInformation {
12928                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12929                    documentation: None,
12930                },
12931            ]),
12932            active_parameter: None,
12933        }],
12934        active_signature: Some(0),
12935        active_parameter: Some(0),
12936    };
12937    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12938    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12939        .await;
12940    cx.editor(|editor, _, _| {
12941        assert!(editor.signature_help_state.is_shown());
12942    });
12943
12944    // Restore the popover with more parameter input
12945    cx.set_state(indoc! {"
12946        fn main() {
12947            sample(param1, param2ˇ);
12948        }
12949
12950        fn sample(param1: u8, param2: u8) {}
12951    "});
12952
12953    let mocked_response = lsp::SignatureHelp {
12954        signatures: vec![lsp::SignatureInformation {
12955            label: "fn sample(param1: u8, param2: u8)".to_string(),
12956            documentation: None,
12957            parameters: Some(vec![
12958                lsp::ParameterInformation {
12959                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12960                    documentation: None,
12961                },
12962                lsp::ParameterInformation {
12963                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12964                    documentation: None,
12965                },
12966            ]),
12967            active_parameter: None,
12968        }],
12969        active_signature: Some(0),
12970        active_parameter: Some(1),
12971    };
12972    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12973    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12974        .await;
12975
12976    // When selecting a range, the popover is gone.
12977    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
12978    cx.update_editor(|editor, window, cx| {
12979        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12980            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
12981        })
12982    });
12983    cx.assert_editor_state(indoc! {"
12984        fn main() {
12985            sample(param1, «ˇparam2»);
12986        }
12987
12988        fn sample(param1: u8, param2: u8) {}
12989    "});
12990    cx.editor(|editor, _, _| {
12991        assert!(!editor.signature_help_state.is_shown());
12992    });
12993
12994    // When unselecting again, the popover is back if within the brackets.
12995    cx.update_editor(|editor, window, cx| {
12996        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12997            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
12998        })
12999    });
13000    cx.assert_editor_state(indoc! {"
13001        fn main() {
13002            sample(param1, ˇparam2);
13003        }
13004
13005        fn sample(param1: u8, param2: u8) {}
13006    "});
13007    handle_signature_help_request(&mut cx, mocked_response).await;
13008    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13009        .await;
13010    cx.editor(|editor, _, _| {
13011        assert!(editor.signature_help_state.is_shown());
13012    });
13013
13014    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13015    cx.update_editor(|editor, window, cx| {
13016        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13017            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13018            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13019        })
13020    });
13021    cx.assert_editor_state(indoc! {"
13022        fn main() {
13023            sample(param1, ˇparam2);
13024        }
13025
13026        fn sample(param1: u8, param2: u8) {}
13027    "});
13028
13029    let mocked_response = lsp::SignatureHelp {
13030        signatures: vec![lsp::SignatureInformation {
13031            label: "fn sample(param1: u8, param2: u8)".to_string(),
13032            documentation: None,
13033            parameters: Some(vec![
13034                lsp::ParameterInformation {
13035                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13036                    documentation: None,
13037                },
13038                lsp::ParameterInformation {
13039                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13040                    documentation: None,
13041                },
13042            ]),
13043            active_parameter: None,
13044        }],
13045        active_signature: Some(0),
13046        active_parameter: Some(1),
13047    };
13048    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13049    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13050        .await;
13051    cx.update_editor(|editor, _, cx| {
13052        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13053    });
13054    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13055        .await;
13056    cx.update_editor(|editor, window, cx| {
13057        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13058            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13059        })
13060    });
13061    cx.assert_editor_state(indoc! {"
13062        fn main() {
13063            sample(param1, «ˇparam2»);
13064        }
13065
13066        fn sample(param1: u8, param2: u8) {}
13067    "});
13068    cx.update_editor(|editor, window, cx| {
13069        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13070            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13071        })
13072    });
13073    cx.assert_editor_state(indoc! {"
13074        fn main() {
13075            sample(param1, ˇparam2);
13076        }
13077
13078        fn sample(param1: u8, param2: u8) {}
13079    "});
13080    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13081        .await;
13082}
13083
13084#[gpui::test]
13085async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13086    init_test(cx, |_| {});
13087
13088    let mut cx = EditorLspTestContext::new_rust(
13089        lsp::ServerCapabilities {
13090            signature_help_provider: Some(lsp::SignatureHelpOptions {
13091                ..Default::default()
13092            }),
13093            ..Default::default()
13094        },
13095        cx,
13096    )
13097    .await;
13098
13099    cx.set_state(indoc! {"
13100        fn main() {
13101            overloadedˇ
13102        }
13103    "});
13104
13105    cx.update_editor(|editor, window, cx| {
13106        editor.handle_input("(", window, cx);
13107        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13108    });
13109
13110    // Mock response with 3 signatures
13111    let mocked_response = lsp::SignatureHelp {
13112        signatures: vec![
13113            lsp::SignatureInformation {
13114                label: "fn overloaded(x: i32)".to_string(),
13115                documentation: None,
13116                parameters: Some(vec![lsp::ParameterInformation {
13117                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13118                    documentation: None,
13119                }]),
13120                active_parameter: None,
13121            },
13122            lsp::SignatureInformation {
13123                label: "fn overloaded(x: i32, y: i32)".to_string(),
13124                documentation: None,
13125                parameters: Some(vec![
13126                    lsp::ParameterInformation {
13127                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13128                        documentation: None,
13129                    },
13130                    lsp::ParameterInformation {
13131                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13132                        documentation: None,
13133                    },
13134                ]),
13135                active_parameter: None,
13136            },
13137            lsp::SignatureInformation {
13138                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13139                documentation: None,
13140                parameters: Some(vec![
13141                    lsp::ParameterInformation {
13142                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13143                        documentation: None,
13144                    },
13145                    lsp::ParameterInformation {
13146                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13147                        documentation: None,
13148                    },
13149                    lsp::ParameterInformation {
13150                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13151                        documentation: None,
13152                    },
13153                ]),
13154                active_parameter: None,
13155            },
13156        ],
13157        active_signature: Some(1),
13158        active_parameter: Some(0),
13159    };
13160    handle_signature_help_request(&mut cx, mocked_response).await;
13161
13162    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13163        .await;
13164
13165    // Verify we have multiple signatures and the right one is selected
13166    cx.editor(|editor, _, _| {
13167        let popover = editor.signature_help_state.popover().cloned().unwrap();
13168        assert_eq!(popover.signatures.len(), 3);
13169        // active_signature was 1, so that should be the current
13170        assert_eq!(popover.current_signature, 1);
13171        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13172        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13173        assert_eq!(
13174            popover.signatures[2].label,
13175            "fn overloaded(x: i32, y: i32, z: i32)"
13176        );
13177    });
13178
13179    // Test navigation functionality
13180    cx.update_editor(|editor, window, cx| {
13181        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13182    });
13183
13184    cx.editor(|editor, _, _| {
13185        let popover = editor.signature_help_state.popover().cloned().unwrap();
13186        assert_eq!(popover.current_signature, 2);
13187    });
13188
13189    // Test wrap around
13190    cx.update_editor(|editor, window, cx| {
13191        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13192    });
13193
13194    cx.editor(|editor, _, _| {
13195        let popover = editor.signature_help_state.popover().cloned().unwrap();
13196        assert_eq!(popover.current_signature, 0);
13197    });
13198
13199    // Test previous navigation
13200    cx.update_editor(|editor, window, cx| {
13201        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13202    });
13203
13204    cx.editor(|editor, _, _| {
13205        let popover = editor.signature_help_state.popover().cloned().unwrap();
13206        assert_eq!(popover.current_signature, 2);
13207    });
13208}
13209
13210#[gpui::test]
13211async fn test_completion_mode(cx: &mut TestAppContext) {
13212    init_test(cx, |_| {});
13213    let mut cx = EditorLspTestContext::new_rust(
13214        lsp::ServerCapabilities {
13215            completion_provider: Some(lsp::CompletionOptions {
13216                resolve_provider: Some(true),
13217                ..Default::default()
13218            }),
13219            ..Default::default()
13220        },
13221        cx,
13222    )
13223    .await;
13224
13225    struct Run {
13226        run_description: &'static str,
13227        initial_state: String,
13228        buffer_marked_text: String,
13229        completion_label: &'static str,
13230        completion_text: &'static str,
13231        expected_with_insert_mode: String,
13232        expected_with_replace_mode: String,
13233        expected_with_replace_subsequence_mode: String,
13234        expected_with_replace_suffix_mode: String,
13235    }
13236
13237    let runs = [
13238        Run {
13239            run_description: "Start of word matches completion text",
13240            initial_state: "before ediˇ after".into(),
13241            buffer_marked_text: "before <edi|> after".into(),
13242            completion_label: "editor",
13243            completion_text: "editor",
13244            expected_with_insert_mode: "before editorˇ after".into(),
13245            expected_with_replace_mode: "before editorˇ after".into(),
13246            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13247            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13248        },
13249        Run {
13250            run_description: "Accept same text at the middle of the word",
13251            initial_state: "before ediˇtor after".into(),
13252            buffer_marked_text: "before <edi|tor> after".into(),
13253            completion_label: "editor",
13254            completion_text: "editor",
13255            expected_with_insert_mode: "before editorˇtor after".into(),
13256            expected_with_replace_mode: "before editorˇ after".into(),
13257            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13258            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13259        },
13260        Run {
13261            run_description: "End of word matches completion text -- cursor at end",
13262            initial_state: "before torˇ after".into(),
13263            buffer_marked_text: "before <tor|> after".into(),
13264            completion_label: "editor",
13265            completion_text: "editor",
13266            expected_with_insert_mode: "before editorˇ after".into(),
13267            expected_with_replace_mode: "before editorˇ after".into(),
13268            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13269            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13270        },
13271        Run {
13272            run_description: "End of word matches completion text -- cursor at start",
13273            initial_state: "before ˇtor after".into(),
13274            buffer_marked_text: "before <|tor> after".into(),
13275            completion_label: "editor",
13276            completion_text: "editor",
13277            expected_with_insert_mode: "before editorˇtor after".into(),
13278            expected_with_replace_mode: "before editorˇ after".into(),
13279            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13280            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13281        },
13282        Run {
13283            run_description: "Prepend text containing whitespace",
13284            initial_state: "pˇfield: bool".into(),
13285            buffer_marked_text: "<p|field>: bool".into(),
13286            completion_label: "pub ",
13287            completion_text: "pub ",
13288            expected_with_insert_mode: "pub ˇfield: bool".into(),
13289            expected_with_replace_mode: "pub ˇ: bool".into(),
13290            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13291            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13292        },
13293        Run {
13294            run_description: "Add element to start of list",
13295            initial_state: "[element_ˇelement_2]".into(),
13296            buffer_marked_text: "[<element_|element_2>]".into(),
13297            completion_label: "element_1",
13298            completion_text: "element_1",
13299            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13300            expected_with_replace_mode: "[element_1ˇ]".into(),
13301            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13302            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13303        },
13304        Run {
13305            run_description: "Add element to start of list -- first and second elements are equal",
13306            initial_state: "[elˇelement]".into(),
13307            buffer_marked_text: "[<el|element>]".into(),
13308            completion_label: "element",
13309            completion_text: "element",
13310            expected_with_insert_mode: "[elementˇelement]".into(),
13311            expected_with_replace_mode: "[elementˇ]".into(),
13312            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13313            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13314        },
13315        Run {
13316            run_description: "Ends with matching suffix",
13317            initial_state: "SubˇError".into(),
13318            buffer_marked_text: "<Sub|Error>".into(),
13319            completion_label: "SubscriptionError",
13320            completion_text: "SubscriptionError",
13321            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13322            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13323            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13324            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13325        },
13326        Run {
13327            run_description: "Suffix is a subsequence -- contiguous",
13328            initial_state: "SubˇErr".into(),
13329            buffer_marked_text: "<Sub|Err>".into(),
13330            completion_label: "SubscriptionError",
13331            completion_text: "SubscriptionError",
13332            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13333            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13334            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13335            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13336        },
13337        Run {
13338            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13339            initial_state: "Suˇscrirr".into(),
13340            buffer_marked_text: "<Su|scrirr>".into(),
13341            completion_label: "SubscriptionError",
13342            completion_text: "SubscriptionError",
13343            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13344            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13345            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13346            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13347        },
13348        Run {
13349            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13350            initial_state: "foo(indˇix)".into(),
13351            buffer_marked_text: "foo(<ind|ix>)".into(),
13352            completion_label: "node_index",
13353            completion_text: "node_index",
13354            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13355            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13356            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13357            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13358        },
13359        Run {
13360            run_description: "Replace range ends before cursor - should extend to cursor",
13361            initial_state: "before editˇo after".into(),
13362            buffer_marked_text: "before <{ed}>it|o after".into(),
13363            completion_label: "editor",
13364            completion_text: "editor",
13365            expected_with_insert_mode: "before editorˇo after".into(),
13366            expected_with_replace_mode: "before editorˇo after".into(),
13367            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13368            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13369        },
13370        Run {
13371            run_description: "Uses label for suffix matching",
13372            initial_state: "before ediˇtor after".into(),
13373            buffer_marked_text: "before <edi|tor> after".into(),
13374            completion_label: "editor",
13375            completion_text: "editor()",
13376            expected_with_insert_mode: "before editor()ˇtor after".into(),
13377            expected_with_replace_mode: "before editor()ˇ after".into(),
13378            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13379            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13380        },
13381        Run {
13382            run_description: "Case insensitive subsequence and suffix matching",
13383            initial_state: "before EDiˇtoR after".into(),
13384            buffer_marked_text: "before <EDi|toR> after".into(),
13385            completion_label: "editor",
13386            completion_text: "editor",
13387            expected_with_insert_mode: "before editorˇtoR after".into(),
13388            expected_with_replace_mode: "before editorˇ after".into(),
13389            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13390            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13391        },
13392    ];
13393
13394    for run in runs {
13395        let run_variations = [
13396            (LspInsertMode::Insert, run.expected_with_insert_mode),
13397            (LspInsertMode::Replace, run.expected_with_replace_mode),
13398            (
13399                LspInsertMode::ReplaceSubsequence,
13400                run.expected_with_replace_subsequence_mode,
13401            ),
13402            (
13403                LspInsertMode::ReplaceSuffix,
13404                run.expected_with_replace_suffix_mode,
13405            ),
13406        ];
13407
13408        for (lsp_insert_mode, expected_text) in run_variations {
13409            eprintln!(
13410                "run = {:?}, mode = {lsp_insert_mode:.?}",
13411                run.run_description,
13412            );
13413
13414            update_test_language_settings(&mut cx, |settings| {
13415                settings.defaults.completions = Some(CompletionSettingsContent {
13416                    lsp_insert_mode: Some(lsp_insert_mode),
13417                    words: Some(WordsCompletionMode::Disabled),
13418                    words_min_length: Some(0),
13419                    ..Default::default()
13420                });
13421            });
13422
13423            cx.set_state(&run.initial_state);
13424            cx.update_editor(|editor, window, cx| {
13425                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13426            });
13427
13428            let counter = Arc::new(AtomicUsize::new(0));
13429            handle_completion_request_with_insert_and_replace(
13430                &mut cx,
13431                &run.buffer_marked_text,
13432                vec![(run.completion_label, run.completion_text)],
13433                counter.clone(),
13434            )
13435            .await;
13436            cx.condition(|editor, _| editor.context_menu_visible())
13437                .await;
13438            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13439
13440            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13441                editor
13442                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13443                    .unwrap()
13444            });
13445            cx.assert_editor_state(&expected_text);
13446            handle_resolve_completion_request(&mut cx, None).await;
13447            apply_additional_edits.await.unwrap();
13448        }
13449    }
13450}
13451
13452#[gpui::test]
13453async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13454    init_test(cx, |_| {});
13455    let mut cx = EditorLspTestContext::new_rust(
13456        lsp::ServerCapabilities {
13457            completion_provider: Some(lsp::CompletionOptions {
13458                resolve_provider: Some(true),
13459                ..Default::default()
13460            }),
13461            ..Default::default()
13462        },
13463        cx,
13464    )
13465    .await;
13466
13467    let initial_state = "SubˇError";
13468    let buffer_marked_text = "<Sub|Error>";
13469    let completion_text = "SubscriptionError";
13470    let expected_with_insert_mode = "SubscriptionErrorˇError";
13471    let expected_with_replace_mode = "SubscriptionErrorˇ";
13472
13473    update_test_language_settings(&mut cx, |settings| {
13474        settings.defaults.completions = Some(CompletionSettingsContent {
13475            words: Some(WordsCompletionMode::Disabled),
13476            words_min_length: Some(0),
13477            // set the opposite here to ensure that the action is overriding the default behavior
13478            lsp_insert_mode: Some(LspInsertMode::Insert),
13479            ..Default::default()
13480        });
13481    });
13482
13483    cx.set_state(initial_state);
13484    cx.update_editor(|editor, window, cx| {
13485        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13486    });
13487
13488    let counter = Arc::new(AtomicUsize::new(0));
13489    handle_completion_request_with_insert_and_replace(
13490        &mut cx,
13491        buffer_marked_text,
13492        vec![(completion_text, completion_text)],
13493        counter.clone(),
13494    )
13495    .await;
13496    cx.condition(|editor, _| editor.context_menu_visible())
13497        .await;
13498    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13499
13500    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13501        editor
13502            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13503            .unwrap()
13504    });
13505    cx.assert_editor_state(expected_with_replace_mode);
13506    handle_resolve_completion_request(&mut cx, None).await;
13507    apply_additional_edits.await.unwrap();
13508
13509    update_test_language_settings(&mut cx, |settings| {
13510        settings.defaults.completions = Some(CompletionSettingsContent {
13511            words: Some(WordsCompletionMode::Disabled),
13512            words_min_length: Some(0),
13513            // set the opposite here to ensure that the action is overriding the default behavior
13514            lsp_insert_mode: Some(LspInsertMode::Replace),
13515            ..Default::default()
13516        });
13517    });
13518
13519    cx.set_state(initial_state);
13520    cx.update_editor(|editor, window, cx| {
13521        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13522    });
13523    handle_completion_request_with_insert_and_replace(
13524        &mut cx,
13525        buffer_marked_text,
13526        vec![(completion_text, completion_text)],
13527        counter.clone(),
13528    )
13529    .await;
13530    cx.condition(|editor, _| editor.context_menu_visible())
13531        .await;
13532    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13533
13534    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13535        editor
13536            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13537            .unwrap()
13538    });
13539    cx.assert_editor_state(expected_with_insert_mode);
13540    handle_resolve_completion_request(&mut cx, None).await;
13541    apply_additional_edits.await.unwrap();
13542}
13543
13544#[gpui::test]
13545async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
13546    init_test(cx, |_| {});
13547    let mut cx = EditorLspTestContext::new_rust(
13548        lsp::ServerCapabilities {
13549            completion_provider: Some(lsp::CompletionOptions {
13550                resolve_provider: Some(true),
13551                ..Default::default()
13552            }),
13553            ..Default::default()
13554        },
13555        cx,
13556    )
13557    .await;
13558
13559    // scenario: surrounding text matches completion text
13560    let completion_text = "to_offset";
13561    let initial_state = indoc! {"
13562        1. buf.to_offˇsuffix
13563        2. buf.to_offˇsuf
13564        3. buf.to_offˇfix
13565        4. buf.to_offˇ
13566        5. into_offˇensive
13567        6. ˇsuffix
13568        7. let ˇ //
13569        8. aaˇzz
13570        9. buf.to_off«zzzzzˇ»suffix
13571        10. buf.«ˇzzzzz»suffix
13572        11. to_off«ˇzzzzz»
13573
13574        buf.to_offˇsuffix  // newest cursor
13575    "};
13576    let completion_marked_buffer = indoc! {"
13577        1. buf.to_offsuffix
13578        2. buf.to_offsuf
13579        3. buf.to_offfix
13580        4. buf.to_off
13581        5. into_offensive
13582        6. suffix
13583        7. let  //
13584        8. aazz
13585        9. buf.to_offzzzzzsuffix
13586        10. buf.zzzzzsuffix
13587        11. to_offzzzzz
13588
13589        buf.<to_off|suffix>  // newest cursor
13590    "};
13591    let expected = indoc! {"
13592        1. buf.to_offsetˇ
13593        2. buf.to_offsetˇsuf
13594        3. buf.to_offsetˇfix
13595        4. buf.to_offsetˇ
13596        5. into_offsetˇensive
13597        6. to_offsetˇsuffix
13598        7. let to_offsetˇ //
13599        8. aato_offsetˇzz
13600        9. buf.to_offsetˇ
13601        10. buf.to_offsetˇsuffix
13602        11. to_offsetˇ
13603
13604        buf.to_offsetˇ  // newest cursor
13605    "};
13606    cx.set_state(initial_state);
13607    cx.update_editor(|editor, window, cx| {
13608        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13609    });
13610    handle_completion_request_with_insert_and_replace(
13611        &mut cx,
13612        completion_marked_buffer,
13613        vec![(completion_text, completion_text)],
13614        Arc::new(AtomicUsize::new(0)),
13615    )
13616    .await;
13617    cx.condition(|editor, _| editor.context_menu_visible())
13618        .await;
13619    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13620        editor
13621            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13622            .unwrap()
13623    });
13624    cx.assert_editor_state(expected);
13625    handle_resolve_completion_request(&mut cx, None).await;
13626    apply_additional_edits.await.unwrap();
13627
13628    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
13629    let completion_text = "foo_and_bar";
13630    let initial_state = indoc! {"
13631        1. ooanbˇ
13632        2. zooanbˇ
13633        3. ooanbˇz
13634        4. zooanbˇz
13635        5. ooanˇ
13636        6. oanbˇ
13637
13638        ooanbˇ
13639    "};
13640    let completion_marked_buffer = indoc! {"
13641        1. ooanb
13642        2. zooanb
13643        3. ooanbz
13644        4. zooanbz
13645        5. ooan
13646        6. oanb
13647
13648        <ooanb|>
13649    "};
13650    let expected = indoc! {"
13651        1. foo_and_barˇ
13652        2. zfoo_and_barˇ
13653        3. foo_and_barˇz
13654        4. zfoo_and_barˇz
13655        5. ooanfoo_and_barˇ
13656        6. oanbfoo_and_barˇ
13657
13658        foo_and_barˇ
13659    "};
13660    cx.set_state(initial_state);
13661    cx.update_editor(|editor, window, cx| {
13662        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13663    });
13664    handle_completion_request_with_insert_and_replace(
13665        &mut cx,
13666        completion_marked_buffer,
13667        vec![(completion_text, completion_text)],
13668        Arc::new(AtomicUsize::new(0)),
13669    )
13670    .await;
13671    cx.condition(|editor, _| editor.context_menu_visible())
13672        .await;
13673    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13674        editor
13675            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13676            .unwrap()
13677    });
13678    cx.assert_editor_state(expected);
13679    handle_resolve_completion_request(&mut cx, None).await;
13680    apply_additional_edits.await.unwrap();
13681
13682    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
13683    // (expects the same as if it was inserted at the end)
13684    let completion_text = "foo_and_bar";
13685    let initial_state = indoc! {"
13686        1. ooˇanb
13687        2. zooˇanb
13688        3. ooˇanbz
13689        4. zooˇanbz
13690
13691        ooˇanb
13692    "};
13693    let completion_marked_buffer = indoc! {"
13694        1. ooanb
13695        2. zooanb
13696        3. ooanbz
13697        4. zooanbz
13698
13699        <oo|anb>
13700    "};
13701    let expected = indoc! {"
13702        1. foo_and_barˇ
13703        2. zfoo_and_barˇ
13704        3. foo_and_barˇz
13705        4. zfoo_and_barˇz
13706
13707        foo_and_barˇ
13708    "};
13709    cx.set_state(initial_state);
13710    cx.update_editor(|editor, window, cx| {
13711        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13712    });
13713    handle_completion_request_with_insert_and_replace(
13714        &mut cx,
13715        completion_marked_buffer,
13716        vec![(completion_text, completion_text)],
13717        Arc::new(AtomicUsize::new(0)),
13718    )
13719    .await;
13720    cx.condition(|editor, _| editor.context_menu_visible())
13721        .await;
13722    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13723        editor
13724            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13725            .unwrap()
13726    });
13727    cx.assert_editor_state(expected);
13728    handle_resolve_completion_request(&mut cx, None).await;
13729    apply_additional_edits.await.unwrap();
13730}
13731
13732// This used to crash
13733#[gpui::test]
13734async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
13735    init_test(cx, |_| {});
13736
13737    let buffer_text = indoc! {"
13738        fn main() {
13739            10.satu;
13740
13741            //
13742            // separate cursors so they open in different excerpts (manually reproducible)
13743            //
13744
13745            10.satu20;
13746        }
13747    "};
13748    let multibuffer_text_with_selections = indoc! {"
13749        fn main() {
13750            10.satuˇ;
13751
13752            //
13753
13754            //
13755
13756            10.satuˇ20;
13757        }
13758    "};
13759    let expected_multibuffer = indoc! {"
13760        fn main() {
13761            10.saturating_sub()ˇ;
13762
13763            //
13764
13765            //
13766
13767            10.saturating_sub()ˇ;
13768        }
13769    "};
13770
13771    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
13772    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
13773
13774    let fs = FakeFs::new(cx.executor());
13775    fs.insert_tree(
13776        path!("/a"),
13777        json!({
13778            "main.rs": buffer_text,
13779        }),
13780    )
13781    .await;
13782
13783    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13784    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13785    language_registry.add(rust_lang());
13786    let mut fake_servers = language_registry.register_fake_lsp(
13787        "Rust",
13788        FakeLspAdapter {
13789            capabilities: lsp::ServerCapabilities {
13790                completion_provider: Some(lsp::CompletionOptions {
13791                    resolve_provider: None,
13792                    ..lsp::CompletionOptions::default()
13793                }),
13794                ..lsp::ServerCapabilities::default()
13795            },
13796            ..FakeLspAdapter::default()
13797        },
13798    );
13799    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13800    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13801    let buffer = project
13802        .update(cx, |project, cx| {
13803            project.open_local_buffer(path!("/a/main.rs"), cx)
13804        })
13805        .await
13806        .unwrap();
13807
13808    let multi_buffer = cx.new(|cx| {
13809        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
13810        multi_buffer.push_excerpts(
13811            buffer.clone(),
13812            [ExcerptRange::new(0..first_excerpt_end)],
13813            cx,
13814        );
13815        multi_buffer.push_excerpts(
13816            buffer.clone(),
13817            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
13818            cx,
13819        );
13820        multi_buffer
13821    });
13822
13823    let editor = workspace
13824        .update(cx, |_, window, cx| {
13825            cx.new(|cx| {
13826                Editor::new(
13827                    EditorMode::Full {
13828                        scale_ui_elements_with_buffer_font_size: false,
13829                        show_active_line_background: false,
13830                        sized_by_content: false,
13831                    },
13832                    multi_buffer.clone(),
13833                    Some(project.clone()),
13834                    window,
13835                    cx,
13836                )
13837            })
13838        })
13839        .unwrap();
13840
13841    let pane = workspace
13842        .update(cx, |workspace, _, _| workspace.active_pane().clone())
13843        .unwrap();
13844    pane.update_in(cx, |pane, window, cx| {
13845        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
13846    });
13847
13848    let fake_server = fake_servers.next().await.unwrap();
13849
13850    editor.update_in(cx, |editor, window, cx| {
13851        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13852            s.select_ranges([
13853                Point::new(1, 11)..Point::new(1, 11),
13854                Point::new(7, 11)..Point::new(7, 11),
13855            ])
13856        });
13857
13858        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
13859    });
13860
13861    editor.update_in(cx, |editor, window, cx| {
13862        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13863    });
13864
13865    fake_server
13866        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13867            let completion_item = lsp::CompletionItem {
13868                label: "saturating_sub()".into(),
13869                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
13870                    lsp::InsertReplaceEdit {
13871                        new_text: "saturating_sub()".to_owned(),
13872                        insert: lsp::Range::new(
13873                            lsp::Position::new(7, 7),
13874                            lsp::Position::new(7, 11),
13875                        ),
13876                        replace: lsp::Range::new(
13877                            lsp::Position::new(7, 7),
13878                            lsp::Position::new(7, 13),
13879                        ),
13880                    },
13881                )),
13882                ..lsp::CompletionItem::default()
13883            };
13884
13885            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
13886        })
13887        .next()
13888        .await
13889        .unwrap();
13890
13891    cx.condition(&editor, |editor, _| editor.context_menu_visible())
13892        .await;
13893
13894    editor
13895        .update_in(cx, |editor, window, cx| {
13896            editor
13897                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13898                .unwrap()
13899        })
13900        .await
13901        .unwrap();
13902
13903    editor.update(cx, |editor, cx| {
13904        assert_text_with_selections(editor, expected_multibuffer, cx);
13905    })
13906}
13907
13908#[gpui::test]
13909async fn test_completion(cx: &mut TestAppContext) {
13910    init_test(cx, |_| {});
13911
13912    let mut cx = EditorLspTestContext::new_rust(
13913        lsp::ServerCapabilities {
13914            completion_provider: Some(lsp::CompletionOptions {
13915                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13916                resolve_provider: Some(true),
13917                ..Default::default()
13918            }),
13919            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13920            ..Default::default()
13921        },
13922        cx,
13923    )
13924    .await;
13925    let counter = Arc::new(AtomicUsize::new(0));
13926
13927    cx.set_state(indoc! {"
13928        oneˇ
13929        two
13930        three
13931    "});
13932    cx.simulate_keystroke(".");
13933    handle_completion_request(
13934        indoc! {"
13935            one.|<>
13936            two
13937            three
13938        "},
13939        vec!["first_completion", "second_completion"],
13940        true,
13941        counter.clone(),
13942        &mut cx,
13943    )
13944    .await;
13945    cx.condition(|editor, _| editor.context_menu_visible())
13946        .await;
13947    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13948
13949    let _handler = handle_signature_help_request(
13950        &mut cx,
13951        lsp::SignatureHelp {
13952            signatures: vec![lsp::SignatureInformation {
13953                label: "test signature".to_string(),
13954                documentation: None,
13955                parameters: Some(vec![lsp::ParameterInformation {
13956                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
13957                    documentation: None,
13958                }]),
13959                active_parameter: None,
13960            }],
13961            active_signature: None,
13962            active_parameter: None,
13963        },
13964    );
13965    cx.update_editor(|editor, window, cx| {
13966        assert!(
13967            !editor.signature_help_state.is_shown(),
13968            "No signature help was called for"
13969        );
13970        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13971    });
13972    cx.run_until_parked();
13973    cx.update_editor(|editor, _, _| {
13974        assert!(
13975            !editor.signature_help_state.is_shown(),
13976            "No signature help should be shown when completions menu is open"
13977        );
13978    });
13979
13980    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13981        editor.context_menu_next(&Default::default(), window, cx);
13982        editor
13983            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13984            .unwrap()
13985    });
13986    cx.assert_editor_state(indoc! {"
13987        one.second_completionˇ
13988        two
13989        three
13990    "});
13991
13992    handle_resolve_completion_request(
13993        &mut cx,
13994        Some(vec![
13995            (
13996                //This overlaps with the primary completion edit which is
13997                //misbehavior from the LSP spec, test that we filter it out
13998                indoc! {"
13999                    one.second_ˇcompletion
14000                    two
14001                    threeˇ
14002                "},
14003                "overlapping additional edit",
14004            ),
14005            (
14006                indoc! {"
14007                    one.second_completion
14008                    two
14009                    threeˇ
14010                "},
14011                "\nadditional edit",
14012            ),
14013        ]),
14014    )
14015    .await;
14016    apply_additional_edits.await.unwrap();
14017    cx.assert_editor_state(indoc! {"
14018        one.second_completionˇ
14019        two
14020        three
14021        additional edit
14022    "});
14023
14024    cx.set_state(indoc! {"
14025        one.second_completion
14026        twoˇ
14027        threeˇ
14028        additional edit
14029    "});
14030    cx.simulate_keystroke(" ");
14031    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14032    cx.simulate_keystroke("s");
14033    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14034
14035    cx.assert_editor_state(indoc! {"
14036        one.second_completion
14037        two sˇ
14038        three sˇ
14039        additional edit
14040    "});
14041    handle_completion_request(
14042        indoc! {"
14043            one.second_completion
14044            two s
14045            three <s|>
14046            additional edit
14047        "},
14048        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14049        true,
14050        counter.clone(),
14051        &mut cx,
14052    )
14053    .await;
14054    cx.condition(|editor, _| editor.context_menu_visible())
14055        .await;
14056    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14057
14058    cx.simulate_keystroke("i");
14059
14060    handle_completion_request(
14061        indoc! {"
14062            one.second_completion
14063            two si
14064            three <si|>
14065            additional edit
14066        "},
14067        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14068        true,
14069        counter.clone(),
14070        &mut cx,
14071    )
14072    .await;
14073    cx.condition(|editor, _| editor.context_menu_visible())
14074        .await;
14075    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14076
14077    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14078        editor
14079            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14080            .unwrap()
14081    });
14082    cx.assert_editor_state(indoc! {"
14083        one.second_completion
14084        two sixth_completionˇ
14085        three sixth_completionˇ
14086        additional edit
14087    "});
14088
14089    apply_additional_edits.await.unwrap();
14090
14091    update_test_language_settings(&mut cx, |settings| {
14092        settings.defaults.show_completions_on_input = Some(false);
14093    });
14094    cx.set_state("editorˇ");
14095    cx.simulate_keystroke(".");
14096    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14097    cx.simulate_keystrokes("c l o");
14098    cx.assert_editor_state("editor.cloˇ");
14099    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14100    cx.update_editor(|editor, window, cx| {
14101        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14102    });
14103    handle_completion_request(
14104        "editor.<clo|>",
14105        vec!["close", "clobber"],
14106        true,
14107        counter.clone(),
14108        &mut cx,
14109    )
14110    .await;
14111    cx.condition(|editor, _| editor.context_menu_visible())
14112        .await;
14113    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14114
14115    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14116        editor
14117            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14118            .unwrap()
14119    });
14120    cx.assert_editor_state("editor.clobberˇ");
14121    handle_resolve_completion_request(&mut cx, None).await;
14122    apply_additional_edits.await.unwrap();
14123}
14124
14125#[gpui::test]
14126async fn test_completion_reuse(cx: &mut TestAppContext) {
14127    init_test(cx, |_| {});
14128
14129    let mut cx = EditorLspTestContext::new_rust(
14130        lsp::ServerCapabilities {
14131            completion_provider: Some(lsp::CompletionOptions {
14132                trigger_characters: Some(vec![".".to_string()]),
14133                ..Default::default()
14134            }),
14135            ..Default::default()
14136        },
14137        cx,
14138    )
14139    .await;
14140
14141    let counter = Arc::new(AtomicUsize::new(0));
14142    cx.set_state("objˇ");
14143    cx.simulate_keystroke(".");
14144
14145    // Initial completion request returns complete results
14146    let is_incomplete = false;
14147    handle_completion_request(
14148        "obj.|<>",
14149        vec!["a", "ab", "abc"],
14150        is_incomplete,
14151        counter.clone(),
14152        &mut cx,
14153    )
14154    .await;
14155    cx.run_until_parked();
14156    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14157    cx.assert_editor_state("obj.ˇ");
14158    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14159
14160    // Type "a" - filters existing completions
14161    cx.simulate_keystroke("a");
14162    cx.run_until_parked();
14163    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14164    cx.assert_editor_state("obj.aˇ");
14165    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14166
14167    // Type "b" - filters existing completions
14168    cx.simulate_keystroke("b");
14169    cx.run_until_parked();
14170    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14171    cx.assert_editor_state("obj.abˇ");
14172    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14173
14174    // Type "c" - filters existing completions
14175    cx.simulate_keystroke("c");
14176    cx.run_until_parked();
14177    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14178    cx.assert_editor_state("obj.abcˇ");
14179    check_displayed_completions(vec!["abc"], &mut cx);
14180
14181    // Backspace to delete "c" - filters existing completions
14182    cx.update_editor(|editor, window, cx| {
14183        editor.backspace(&Backspace, window, cx);
14184    });
14185    cx.run_until_parked();
14186    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14187    cx.assert_editor_state("obj.abˇ");
14188    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14189
14190    // Moving cursor to the left dismisses menu.
14191    cx.update_editor(|editor, window, cx| {
14192        editor.move_left(&MoveLeft, window, cx);
14193    });
14194    cx.run_until_parked();
14195    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14196    cx.assert_editor_state("obj.aˇb");
14197    cx.update_editor(|editor, _, _| {
14198        assert_eq!(editor.context_menu_visible(), false);
14199    });
14200
14201    // Type "b" - new request
14202    cx.simulate_keystroke("b");
14203    let is_incomplete = false;
14204    handle_completion_request(
14205        "obj.<ab|>a",
14206        vec!["ab", "abc"],
14207        is_incomplete,
14208        counter.clone(),
14209        &mut cx,
14210    )
14211    .await;
14212    cx.run_until_parked();
14213    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14214    cx.assert_editor_state("obj.abˇb");
14215    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14216
14217    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14218    cx.update_editor(|editor, window, cx| {
14219        editor.backspace(&Backspace, window, cx);
14220    });
14221    let is_incomplete = false;
14222    handle_completion_request(
14223        "obj.<a|>b",
14224        vec!["a", "ab", "abc"],
14225        is_incomplete,
14226        counter.clone(),
14227        &mut cx,
14228    )
14229    .await;
14230    cx.run_until_parked();
14231    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14232    cx.assert_editor_state("obj.aˇb");
14233    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14234
14235    // Backspace to delete "a" - dismisses menu.
14236    cx.update_editor(|editor, window, cx| {
14237        editor.backspace(&Backspace, window, cx);
14238    });
14239    cx.run_until_parked();
14240    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14241    cx.assert_editor_state("obj.ˇb");
14242    cx.update_editor(|editor, _, _| {
14243        assert_eq!(editor.context_menu_visible(), false);
14244    });
14245}
14246
14247#[gpui::test]
14248async fn test_word_completion(cx: &mut TestAppContext) {
14249    let lsp_fetch_timeout_ms = 10;
14250    init_test(cx, |language_settings| {
14251        language_settings.defaults.completions = Some(CompletionSettingsContent {
14252            words_min_length: Some(0),
14253            lsp_fetch_timeout_ms: Some(10),
14254            lsp_insert_mode: Some(LspInsertMode::Insert),
14255            ..Default::default()
14256        });
14257    });
14258
14259    let mut cx = EditorLspTestContext::new_rust(
14260        lsp::ServerCapabilities {
14261            completion_provider: Some(lsp::CompletionOptions {
14262                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14263                ..lsp::CompletionOptions::default()
14264            }),
14265            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14266            ..lsp::ServerCapabilities::default()
14267        },
14268        cx,
14269    )
14270    .await;
14271
14272    let throttle_completions = Arc::new(AtomicBool::new(false));
14273
14274    let lsp_throttle_completions = throttle_completions.clone();
14275    let _completion_requests_handler =
14276        cx.lsp
14277            .server
14278            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14279                let lsp_throttle_completions = lsp_throttle_completions.clone();
14280                let cx = cx.clone();
14281                async move {
14282                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14283                        cx.background_executor()
14284                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14285                            .await;
14286                    }
14287                    Ok(Some(lsp::CompletionResponse::Array(vec![
14288                        lsp::CompletionItem {
14289                            label: "first".into(),
14290                            ..lsp::CompletionItem::default()
14291                        },
14292                        lsp::CompletionItem {
14293                            label: "last".into(),
14294                            ..lsp::CompletionItem::default()
14295                        },
14296                    ])))
14297                }
14298            });
14299
14300    cx.set_state(indoc! {"
14301        oneˇ
14302        two
14303        three
14304    "});
14305    cx.simulate_keystroke(".");
14306    cx.executor().run_until_parked();
14307    cx.condition(|editor, _| editor.context_menu_visible())
14308        .await;
14309    cx.update_editor(|editor, window, cx| {
14310        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14311        {
14312            assert_eq!(
14313                completion_menu_entries(menu),
14314                &["first", "last"],
14315                "When LSP server is fast to reply, no fallback word completions are used"
14316            );
14317        } else {
14318            panic!("expected completion menu to be open");
14319        }
14320        editor.cancel(&Cancel, window, cx);
14321    });
14322    cx.executor().run_until_parked();
14323    cx.condition(|editor, _| !editor.context_menu_visible())
14324        .await;
14325
14326    throttle_completions.store(true, atomic::Ordering::Release);
14327    cx.simulate_keystroke(".");
14328    cx.executor()
14329        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14330    cx.executor().run_until_parked();
14331    cx.condition(|editor, _| editor.context_menu_visible())
14332        .await;
14333    cx.update_editor(|editor, _, _| {
14334        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14335        {
14336            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14337                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14338        } else {
14339            panic!("expected completion menu to be open");
14340        }
14341    });
14342}
14343
14344#[gpui::test]
14345async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14346    init_test(cx, |language_settings| {
14347        language_settings.defaults.completions = Some(CompletionSettingsContent {
14348            words: Some(WordsCompletionMode::Enabled),
14349            words_min_length: Some(0),
14350            lsp_insert_mode: Some(LspInsertMode::Insert),
14351            ..Default::default()
14352        });
14353    });
14354
14355    let mut cx = EditorLspTestContext::new_rust(
14356        lsp::ServerCapabilities {
14357            completion_provider: Some(lsp::CompletionOptions {
14358                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14359                ..lsp::CompletionOptions::default()
14360            }),
14361            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14362            ..lsp::ServerCapabilities::default()
14363        },
14364        cx,
14365    )
14366    .await;
14367
14368    let _completion_requests_handler =
14369        cx.lsp
14370            .server
14371            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14372                Ok(Some(lsp::CompletionResponse::Array(vec![
14373                    lsp::CompletionItem {
14374                        label: "first".into(),
14375                        ..lsp::CompletionItem::default()
14376                    },
14377                    lsp::CompletionItem {
14378                        label: "last".into(),
14379                        ..lsp::CompletionItem::default()
14380                    },
14381                ])))
14382            });
14383
14384    cx.set_state(indoc! {"ˇ
14385        first
14386        last
14387        second
14388    "});
14389    cx.simulate_keystroke(".");
14390    cx.executor().run_until_parked();
14391    cx.condition(|editor, _| editor.context_menu_visible())
14392        .await;
14393    cx.update_editor(|editor, _, _| {
14394        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14395        {
14396            assert_eq!(
14397                completion_menu_entries(menu),
14398                &["first", "last", "second"],
14399                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14400            );
14401        } else {
14402            panic!("expected completion menu to be open");
14403        }
14404    });
14405}
14406
14407#[gpui::test]
14408async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14409    init_test(cx, |language_settings| {
14410        language_settings.defaults.completions = Some(CompletionSettingsContent {
14411            words: Some(WordsCompletionMode::Disabled),
14412            words_min_length: Some(0),
14413            lsp_insert_mode: Some(LspInsertMode::Insert),
14414            ..Default::default()
14415        });
14416    });
14417
14418    let mut cx = EditorLspTestContext::new_rust(
14419        lsp::ServerCapabilities {
14420            completion_provider: Some(lsp::CompletionOptions {
14421                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14422                ..lsp::CompletionOptions::default()
14423            }),
14424            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14425            ..lsp::ServerCapabilities::default()
14426        },
14427        cx,
14428    )
14429    .await;
14430
14431    let _completion_requests_handler =
14432        cx.lsp
14433            .server
14434            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14435                panic!("LSP completions should not be queried when dealing with word completions")
14436            });
14437
14438    cx.set_state(indoc! {"ˇ
14439        first
14440        last
14441        second
14442    "});
14443    cx.update_editor(|editor, window, cx| {
14444        editor.show_word_completions(&ShowWordCompletions, window, cx);
14445    });
14446    cx.executor().run_until_parked();
14447    cx.condition(|editor, _| editor.context_menu_visible())
14448        .await;
14449    cx.update_editor(|editor, _, _| {
14450        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14451        {
14452            assert_eq!(
14453                completion_menu_entries(menu),
14454                &["first", "last", "second"],
14455                "`ShowWordCompletions` action should show word completions"
14456            );
14457        } else {
14458            panic!("expected completion menu to be open");
14459        }
14460    });
14461
14462    cx.simulate_keystroke("l");
14463    cx.executor().run_until_parked();
14464    cx.condition(|editor, _| editor.context_menu_visible())
14465        .await;
14466    cx.update_editor(|editor, _, _| {
14467        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14468        {
14469            assert_eq!(
14470                completion_menu_entries(menu),
14471                &["last"],
14472                "After showing word completions, further editing should filter them and not query the LSP"
14473            );
14474        } else {
14475            panic!("expected completion menu to be open");
14476        }
14477    });
14478}
14479
14480#[gpui::test]
14481async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14482    init_test(cx, |language_settings| {
14483        language_settings.defaults.completions = Some(CompletionSettingsContent {
14484            words_min_length: Some(0),
14485            lsp: Some(false),
14486            lsp_insert_mode: Some(LspInsertMode::Insert),
14487            ..Default::default()
14488        });
14489    });
14490
14491    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14492
14493    cx.set_state(indoc! {"ˇ
14494        0_usize
14495        let
14496        33
14497        4.5f32
14498    "});
14499    cx.update_editor(|editor, window, cx| {
14500        editor.show_completions(&ShowCompletions::default(), window, cx);
14501    });
14502    cx.executor().run_until_parked();
14503    cx.condition(|editor, _| editor.context_menu_visible())
14504        .await;
14505    cx.update_editor(|editor, window, cx| {
14506        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14507        {
14508            assert_eq!(
14509                completion_menu_entries(menu),
14510                &["let"],
14511                "With no digits in the completion query, no digits should be in the word completions"
14512            );
14513        } else {
14514            panic!("expected completion menu to be open");
14515        }
14516        editor.cancel(&Cancel, window, cx);
14517    });
14518
14519    cx.set_state(indoc! {"14520        0_usize
14521        let
14522        3
14523        33.35f32
14524    "});
14525    cx.update_editor(|editor, window, cx| {
14526        editor.show_completions(&ShowCompletions::default(), window, cx);
14527    });
14528    cx.executor().run_until_parked();
14529    cx.condition(|editor, _| editor.context_menu_visible())
14530        .await;
14531    cx.update_editor(|editor, _, _| {
14532        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14533        {
14534            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14535                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14536        } else {
14537            panic!("expected completion menu to be open");
14538        }
14539    });
14540}
14541
14542#[gpui::test]
14543async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
14544    init_test(cx, |language_settings| {
14545        language_settings.defaults.completions = Some(CompletionSettingsContent {
14546            words: Some(WordsCompletionMode::Enabled),
14547            words_min_length: Some(3),
14548            lsp_insert_mode: Some(LspInsertMode::Insert),
14549            ..Default::default()
14550        });
14551    });
14552
14553    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14554    cx.set_state(indoc! {"ˇ
14555        wow
14556        wowen
14557        wowser
14558    "});
14559    cx.simulate_keystroke("w");
14560    cx.executor().run_until_parked();
14561    cx.update_editor(|editor, _, _| {
14562        if editor.context_menu.borrow_mut().is_some() {
14563            panic!(
14564                "expected completion menu to be hidden, as words completion threshold is not met"
14565            );
14566        }
14567    });
14568
14569    cx.update_editor(|editor, window, cx| {
14570        editor.show_word_completions(&ShowWordCompletions, window, cx);
14571    });
14572    cx.executor().run_until_parked();
14573    cx.update_editor(|editor, window, cx| {
14574        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14575        {
14576            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");
14577        } else {
14578            panic!("expected completion menu to be open after the word completions are called with an action");
14579        }
14580
14581        editor.cancel(&Cancel, window, cx);
14582    });
14583    cx.update_editor(|editor, _, _| {
14584        if editor.context_menu.borrow_mut().is_some() {
14585            panic!("expected completion menu to be hidden after canceling");
14586        }
14587    });
14588
14589    cx.simulate_keystroke("o");
14590    cx.executor().run_until_parked();
14591    cx.update_editor(|editor, _, _| {
14592        if editor.context_menu.borrow_mut().is_some() {
14593            panic!(
14594                "expected completion menu to be hidden, as words completion threshold is not met still"
14595            );
14596        }
14597    });
14598
14599    cx.simulate_keystroke("w");
14600    cx.executor().run_until_parked();
14601    cx.update_editor(|editor, _, _| {
14602        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14603        {
14604            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
14605        } else {
14606            panic!("expected completion menu to be open after the word completions threshold is met");
14607        }
14608    });
14609}
14610
14611#[gpui::test]
14612async fn test_word_completions_disabled(cx: &mut TestAppContext) {
14613    init_test(cx, |language_settings| {
14614        language_settings.defaults.completions = Some(CompletionSettingsContent {
14615            words: Some(WordsCompletionMode::Enabled),
14616            words_min_length: Some(0),
14617            lsp_insert_mode: Some(LspInsertMode::Insert),
14618            ..Default::default()
14619        });
14620    });
14621
14622    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14623    cx.update_editor(|editor, _, _| {
14624        editor.disable_word_completions();
14625    });
14626    cx.set_state(indoc! {"ˇ
14627        wow
14628        wowen
14629        wowser
14630    "});
14631    cx.simulate_keystroke("w");
14632    cx.executor().run_until_parked();
14633    cx.update_editor(|editor, _, _| {
14634        if editor.context_menu.borrow_mut().is_some() {
14635            panic!(
14636                "expected completion menu to be hidden, as words completion are disabled for this editor"
14637            );
14638        }
14639    });
14640
14641    cx.update_editor(|editor, window, cx| {
14642        editor.show_word_completions(&ShowWordCompletions, window, cx);
14643    });
14644    cx.executor().run_until_parked();
14645    cx.update_editor(|editor, _, _| {
14646        if editor.context_menu.borrow_mut().is_some() {
14647            panic!(
14648                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
14649            );
14650        }
14651    });
14652}
14653
14654fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
14655    let position = || lsp::Position {
14656        line: params.text_document_position.position.line,
14657        character: params.text_document_position.position.character,
14658    };
14659    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14660        range: lsp::Range {
14661            start: position(),
14662            end: position(),
14663        },
14664        new_text: text.to_string(),
14665    }))
14666}
14667
14668#[gpui::test]
14669async fn test_multiline_completion(cx: &mut TestAppContext) {
14670    init_test(cx, |_| {});
14671
14672    let fs = FakeFs::new(cx.executor());
14673    fs.insert_tree(
14674        path!("/a"),
14675        json!({
14676            "main.ts": "a",
14677        }),
14678    )
14679    .await;
14680
14681    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14682    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14683    let typescript_language = Arc::new(Language::new(
14684        LanguageConfig {
14685            name: "TypeScript".into(),
14686            matcher: LanguageMatcher {
14687                path_suffixes: vec!["ts".to_string()],
14688                ..LanguageMatcher::default()
14689            },
14690            line_comments: vec!["// ".into()],
14691            ..LanguageConfig::default()
14692        },
14693        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14694    ));
14695    language_registry.add(typescript_language.clone());
14696    let mut fake_servers = language_registry.register_fake_lsp(
14697        "TypeScript",
14698        FakeLspAdapter {
14699            capabilities: lsp::ServerCapabilities {
14700                completion_provider: Some(lsp::CompletionOptions {
14701                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14702                    ..lsp::CompletionOptions::default()
14703                }),
14704                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14705                ..lsp::ServerCapabilities::default()
14706            },
14707            // Emulate vtsls label generation
14708            label_for_completion: Some(Box::new(|item, _| {
14709                let text = if let Some(description) = item
14710                    .label_details
14711                    .as_ref()
14712                    .and_then(|label_details| label_details.description.as_ref())
14713                {
14714                    format!("{} {}", item.label, description)
14715                } else if let Some(detail) = &item.detail {
14716                    format!("{} {}", item.label, detail)
14717                } else {
14718                    item.label.clone()
14719                };
14720                let len = text.len();
14721                Some(language::CodeLabel {
14722                    text,
14723                    runs: Vec::new(),
14724                    filter_range: 0..len,
14725                })
14726            })),
14727            ..FakeLspAdapter::default()
14728        },
14729    );
14730    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14731    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14732    let worktree_id = workspace
14733        .update(cx, |workspace, _window, cx| {
14734            workspace.project().update(cx, |project, cx| {
14735                project.worktrees(cx).next().unwrap().read(cx).id()
14736            })
14737        })
14738        .unwrap();
14739    let _buffer = project
14740        .update(cx, |project, cx| {
14741            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
14742        })
14743        .await
14744        .unwrap();
14745    let editor = workspace
14746        .update(cx, |workspace, window, cx| {
14747            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
14748        })
14749        .unwrap()
14750        .await
14751        .unwrap()
14752        .downcast::<Editor>()
14753        .unwrap();
14754    let fake_server = fake_servers.next().await.unwrap();
14755
14756    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
14757    let multiline_label_2 = "a\nb\nc\n";
14758    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
14759    let multiline_description = "d\ne\nf\n";
14760    let multiline_detail_2 = "g\nh\ni\n";
14761
14762    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
14763        move |params, _| async move {
14764            Ok(Some(lsp::CompletionResponse::Array(vec![
14765                lsp::CompletionItem {
14766                    label: multiline_label.to_string(),
14767                    text_edit: gen_text_edit(&params, "new_text_1"),
14768                    ..lsp::CompletionItem::default()
14769                },
14770                lsp::CompletionItem {
14771                    label: "single line label 1".to_string(),
14772                    detail: Some(multiline_detail.to_string()),
14773                    text_edit: gen_text_edit(&params, "new_text_2"),
14774                    ..lsp::CompletionItem::default()
14775                },
14776                lsp::CompletionItem {
14777                    label: "single line label 2".to_string(),
14778                    label_details: Some(lsp::CompletionItemLabelDetails {
14779                        description: Some(multiline_description.to_string()),
14780                        detail: None,
14781                    }),
14782                    text_edit: gen_text_edit(&params, "new_text_2"),
14783                    ..lsp::CompletionItem::default()
14784                },
14785                lsp::CompletionItem {
14786                    label: multiline_label_2.to_string(),
14787                    detail: Some(multiline_detail_2.to_string()),
14788                    text_edit: gen_text_edit(&params, "new_text_3"),
14789                    ..lsp::CompletionItem::default()
14790                },
14791                lsp::CompletionItem {
14792                    label: "Label with many     spaces and \t but without newlines".to_string(),
14793                    detail: Some(
14794                        "Details with many     spaces and \t but without newlines".to_string(),
14795                    ),
14796                    text_edit: gen_text_edit(&params, "new_text_4"),
14797                    ..lsp::CompletionItem::default()
14798                },
14799            ])))
14800        },
14801    );
14802
14803    editor.update_in(cx, |editor, window, cx| {
14804        cx.focus_self(window);
14805        editor.move_to_end(&MoveToEnd, window, cx);
14806        editor.handle_input(".", window, cx);
14807    });
14808    cx.run_until_parked();
14809    completion_handle.next().await.unwrap();
14810
14811    editor.update(cx, |editor, _| {
14812        assert!(editor.context_menu_visible());
14813        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14814        {
14815            let completion_labels = menu
14816                .completions
14817                .borrow()
14818                .iter()
14819                .map(|c| c.label.text.clone())
14820                .collect::<Vec<_>>();
14821            assert_eq!(
14822                completion_labels,
14823                &[
14824                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
14825                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
14826                    "single line label 2 d e f ",
14827                    "a b c g h i ",
14828                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
14829                ],
14830                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
14831            );
14832
14833            for completion in menu
14834                .completions
14835                .borrow()
14836                .iter() {
14837                    assert_eq!(
14838                        completion.label.filter_range,
14839                        0..completion.label.text.len(),
14840                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
14841                    );
14842                }
14843        } else {
14844            panic!("expected completion menu to be open");
14845        }
14846    });
14847}
14848
14849#[gpui::test]
14850async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
14851    init_test(cx, |_| {});
14852    let mut cx = EditorLspTestContext::new_rust(
14853        lsp::ServerCapabilities {
14854            completion_provider: Some(lsp::CompletionOptions {
14855                trigger_characters: Some(vec![".".to_string()]),
14856                ..Default::default()
14857            }),
14858            ..Default::default()
14859        },
14860        cx,
14861    )
14862    .await;
14863    cx.lsp
14864        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14865            Ok(Some(lsp::CompletionResponse::Array(vec![
14866                lsp::CompletionItem {
14867                    label: "first".into(),
14868                    ..Default::default()
14869                },
14870                lsp::CompletionItem {
14871                    label: "last".into(),
14872                    ..Default::default()
14873                },
14874            ])))
14875        });
14876    cx.set_state("variableˇ");
14877    cx.simulate_keystroke(".");
14878    cx.executor().run_until_parked();
14879
14880    cx.update_editor(|editor, _, _| {
14881        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14882        {
14883            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
14884        } else {
14885            panic!("expected completion menu to be open");
14886        }
14887    });
14888
14889    cx.update_editor(|editor, window, cx| {
14890        editor.move_page_down(&MovePageDown::default(), window, cx);
14891        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14892        {
14893            assert!(
14894                menu.selected_item == 1,
14895                "expected PageDown to select the last item from the context menu"
14896            );
14897        } else {
14898            panic!("expected completion menu to stay open after PageDown");
14899        }
14900    });
14901
14902    cx.update_editor(|editor, window, cx| {
14903        editor.move_page_up(&MovePageUp::default(), window, cx);
14904        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14905        {
14906            assert!(
14907                menu.selected_item == 0,
14908                "expected PageUp to select the first item from the context menu"
14909            );
14910        } else {
14911            panic!("expected completion menu to stay open after PageUp");
14912        }
14913    });
14914}
14915
14916#[gpui::test]
14917async fn test_as_is_completions(cx: &mut TestAppContext) {
14918    init_test(cx, |_| {});
14919    let mut cx = EditorLspTestContext::new_rust(
14920        lsp::ServerCapabilities {
14921            completion_provider: Some(lsp::CompletionOptions {
14922                ..Default::default()
14923            }),
14924            ..Default::default()
14925        },
14926        cx,
14927    )
14928    .await;
14929    cx.lsp
14930        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14931            Ok(Some(lsp::CompletionResponse::Array(vec![
14932                lsp::CompletionItem {
14933                    label: "unsafe".into(),
14934                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14935                        range: lsp::Range {
14936                            start: lsp::Position {
14937                                line: 1,
14938                                character: 2,
14939                            },
14940                            end: lsp::Position {
14941                                line: 1,
14942                                character: 3,
14943                            },
14944                        },
14945                        new_text: "unsafe".to_string(),
14946                    })),
14947                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
14948                    ..Default::default()
14949                },
14950            ])))
14951        });
14952    cx.set_state("fn a() {}\n");
14953    cx.executor().run_until_parked();
14954    cx.update_editor(|editor, window, cx| {
14955        editor.show_completions(
14956            &ShowCompletions {
14957                trigger: Some("\n".into()),
14958            },
14959            window,
14960            cx,
14961        );
14962    });
14963    cx.executor().run_until_parked();
14964
14965    cx.update_editor(|editor, window, cx| {
14966        editor.confirm_completion(&Default::default(), window, cx)
14967    });
14968    cx.executor().run_until_parked();
14969    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
14970}
14971
14972#[gpui::test]
14973async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
14974    init_test(cx, |_| {});
14975    let language =
14976        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
14977    let mut cx = EditorLspTestContext::new(
14978        language,
14979        lsp::ServerCapabilities {
14980            completion_provider: Some(lsp::CompletionOptions {
14981                ..lsp::CompletionOptions::default()
14982            }),
14983            ..lsp::ServerCapabilities::default()
14984        },
14985        cx,
14986    )
14987    .await;
14988
14989    cx.set_state(
14990        "#ifndef BAR_H
14991#define BAR_H
14992
14993#include <stdbool.h>
14994
14995int fn_branch(bool do_branch1, bool do_branch2);
14996
14997#endif // BAR_H
14998ˇ",
14999    );
15000    cx.executor().run_until_parked();
15001    cx.update_editor(|editor, window, cx| {
15002        editor.handle_input("#", window, cx);
15003    });
15004    cx.executor().run_until_parked();
15005    cx.update_editor(|editor, window, cx| {
15006        editor.handle_input("i", window, cx);
15007    });
15008    cx.executor().run_until_parked();
15009    cx.update_editor(|editor, window, cx| {
15010        editor.handle_input("n", window, cx);
15011    });
15012    cx.executor().run_until_parked();
15013    cx.assert_editor_state(
15014        "#ifndef BAR_H
15015#define BAR_H
15016
15017#include <stdbool.h>
15018
15019int fn_branch(bool do_branch1, bool do_branch2);
15020
15021#endif // BAR_H
15022#inˇ",
15023    );
15024
15025    cx.lsp
15026        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15027            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15028                is_incomplete: false,
15029                item_defaults: None,
15030                items: vec![lsp::CompletionItem {
15031                    kind: Some(lsp::CompletionItemKind::SNIPPET),
15032                    label_details: Some(lsp::CompletionItemLabelDetails {
15033                        detail: Some("header".to_string()),
15034                        description: None,
15035                    }),
15036                    label: " include".to_string(),
15037                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15038                        range: lsp::Range {
15039                            start: lsp::Position {
15040                                line: 8,
15041                                character: 1,
15042                            },
15043                            end: lsp::Position {
15044                                line: 8,
15045                                character: 1,
15046                            },
15047                        },
15048                        new_text: "include \"$0\"".to_string(),
15049                    })),
15050                    sort_text: Some("40b67681include".to_string()),
15051                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15052                    filter_text: Some("include".to_string()),
15053                    insert_text: Some("include \"$0\"".to_string()),
15054                    ..lsp::CompletionItem::default()
15055                }],
15056            })))
15057        });
15058    cx.update_editor(|editor, window, cx| {
15059        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15060    });
15061    cx.executor().run_until_parked();
15062    cx.update_editor(|editor, window, cx| {
15063        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15064    });
15065    cx.executor().run_until_parked();
15066    cx.assert_editor_state(
15067        "#ifndef BAR_H
15068#define BAR_H
15069
15070#include <stdbool.h>
15071
15072int fn_branch(bool do_branch1, bool do_branch2);
15073
15074#endif // BAR_H
15075#include \"ˇ\"",
15076    );
15077
15078    cx.lsp
15079        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15080            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15081                is_incomplete: true,
15082                item_defaults: None,
15083                items: vec![lsp::CompletionItem {
15084                    kind: Some(lsp::CompletionItemKind::FILE),
15085                    label: "AGL/".to_string(),
15086                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15087                        range: lsp::Range {
15088                            start: lsp::Position {
15089                                line: 8,
15090                                character: 10,
15091                            },
15092                            end: lsp::Position {
15093                                line: 8,
15094                                character: 11,
15095                            },
15096                        },
15097                        new_text: "AGL/".to_string(),
15098                    })),
15099                    sort_text: Some("40b67681AGL/".to_string()),
15100                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15101                    filter_text: Some("AGL/".to_string()),
15102                    insert_text: Some("AGL/".to_string()),
15103                    ..lsp::CompletionItem::default()
15104                }],
15105            })))
15106        });
15107    cx.update_editor(|editor, window, cx| {
15108        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15109    });
15110    cx.executor().run_until_parked();
15111    cx.update_editor(|editor, window, cx| {
15112        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15113    });
15114    cx.executor().run_until_parked();
15115    cx.assert_editor_state(
15116        r##"#ifndef BAR_H
15117#define BAR_H
15118
15119#include <stdbool.h>
15120
15121int fn_branch(bool do_branch1, bool do_branch2);
15122
15123#endif // BAR_H
15124#include "AGL/ˇ"##,
15125    );
15126
15127    cx.update_editor(|editor, window, cx| {
15128        editor.handle_input("\"", window, cx);
15129    });
15130    cx.executor().run_until_parked();
15131    cx.assert_editor_state(
15132        r##"#ifndef BAR_H
15133#define BAR_H
15134
15135#include <stdbool.h>
15136
15137int fn_branch(bool do_branch1, bool do_branch2);
15138
15139#endif // BAR_H
15140#include "AGL/"ˇ"##,
15141    );
15142}
15143
15144#[gpui::test]
15145async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15146    init_test(cx, |_| {});
15147
15148    let mut cx = EditorLspTestContext::new_rust(
15149        lsp::ServerCapabilities {
15150            completion_provider: Some(lsp::CompletionOptions {
15151                trigger_characters: Some(vec![".".to_string()]),
15152                resolve_provider: Some(true),
15153                ..Default::default()
15154            }),
15155            ..Default::default()
15156        },
15157        cx,
15158    )
15159    .await;
15160
15161    cx.set_state("fn main() { let a = 2ˇ; }");
15162    cx.simulate_keystroke(".");
15163    let completion_item = lsp::CompletionItem {
15164        label: "Some".into(),
15165        kind: Some(lsp::CompletionItemKind::SNIPPET),
15166        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15167        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15168            kind: lsp::MarkupKind::Markdown,
15169            value: "```rust\nSome(2)\n```".to_string(),
15170        })),
15171        deprecated: Some(false),
15172        sort_text: Some("Some".to_string()),
15173        filter_text: Some("Some".to_string()),
15174        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15175        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15176            range: lsp::Range {
15177                start: lsp::Position {
15178                    line: 0,
15179                    character: 22,
15180                },
15181                end: lsp::Position {
15182                    line: 0,
15183                    character: 22,
15184                },
15185            },
15186            new_text: "Some(2)".to_string(),
15187        })),
15188        additional_text_edits: Some(vec![lsp::TextEdit {
15189            range: lsp::Range {
15190                start: lsp::Position {
15191                    line: 0,
15192                    character: 20,
15193                },
15194                end: lsp::Position {
15195                    line: 0,
15196                    character: 22,
15197                },
15198            },
15199            new_text: "".to_string(),
15200        }]),
15201        ..Default::default()
15202    };
15203
15204    let closure_completion_item = completion_item.clone();
15205    let counter = Arc::new(AtomicUsize::new(0));
15206    let counter_clone = counter.clone();
15207    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15208        let task_completion_item = closure_completion_item.clone();
15209        counter_clone.fetch_add(1, atomic::Ordering::Release);
15210        async move {
15211            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15212                is_incomplete: true,
15213                item_defaults: None,
15214                items: vec![task_completion_item],
15215            })))
15216        }
15217    });
15218
15219    cx.condition(|editor, _| editor.context_menu_visible())
15220        .await;
15221    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15222    assert!(request.next().await.is_some());
15223    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15224
15225    cx.simulate_keystrokes("S o m");
15226    cx.condition(|editor, _| editor.context_menu_visible())
15227        .await;
15228    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15229    assert!(request.next().await.is_some());
15230    assert!(request.next().await.is_some());
15231    assert!(request.next().await.is_some());
15232    request.close();
15233    assert!(request.next().await.is_none());
15234    assert_eq!(
15235        counter.load(atomic::Ordering::Acquire),
15236        4,
15237        "With the completions menu open, only one LSP request should happen per input"
15238    );
15239}
15240
15241#[gpui::test]
15242async fn test_toggle_comment(cx: &mut TestAppContext) {
15243    init_test(cx, |_| {});
15244    let mut cx = EditorTestContext::new(cx).await;
15245    let language = Arc::new(Language::new(
15246        LanguageConfig {
15247            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15248            ..Default::default()
15249        },
15250        Some(tree_sitter_rust::LANGUAGE.into()),
15251    ));
15252    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15253
15254    // If multiple selections intersect a line, the line is only toggled once.
15255    cx.set_state(indoc! {"
15256        fn a() {
15257            «//b();
15258            ˇ»// «c();
15259            //ˇ»  d();
15260        }
15261    "});
15262
15263    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15264
15265    cx.assert_editor_state(indoc! {"
15266        fn a() {
15267            «b();
15268            c();
15269            ˇ» d();
15270        }
15271    "});
15272
15273    // The comment prefix is inserted at the same column for every line in a
15274    // selection.
15275    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15276
15277    cx.assert_editor_state(indoc! {"
15278        fn a() {
15279            // «b();
15280            // c();
15281            ˇ»//  d();
15282        }
15283    "});
15284
15285    // If a selection ends at the beginning of a line, that line is not toggled.
15286    cx.set_selections_state(indoc! {"
15287        fn a() {
15288            // b();
15289            «// c();
15290        ˇ»    //  d();
15291        }
15292    "});
15293
15294    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15295
15296    cx.assert_editor_state(indoc! {"
15297        fn a() {
15298            // b();
15299            «c();
15300        ˇ»    //  d();
15301        }
15302    "});
15303
15304    // If a selection span a single line and is empty, the line is toggled.
15305    cx.set_state(indoc! {"
15306        fn a() {
15307            a();
15308            b();
15309        ˇ
15310        }
15311    "});
15312
15313    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15314
15315    cx.assert_editor_state(indoc! {"
15316        fn a() {
15317            a();
15318            b();
15319        //•ˇ
15320        }
15321    "});
15322
15323    // If a selection span multiple lines, empty lines are not toggled.
15324    cx.set_state(indoc! {"
15325        fn a() {
15326            «a();
15327
15328            c();ˇ»
15329        }
15330    "});
15331
15332    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15333
15334    cx.assert_editor_state(indoc! {"
15335        fn a() {
15336            // «a();
15337
15338            // c();ˇ»
15339        }
15340    "});
15341
15342    // If a selection includes multiple comment prefixes, all lines are uncommented.
15343    cx.set_state(indoc! {"
15344        fn a() {
15345            «// a();
15346            /// b();
15347            //! c();ˇ»
15348        }
15349    "});
15350
15351    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15352
15353    cx.assert_editor_state(indoc! {"
15354        fn a() {
15355            «a();
15356            b();
15357            c();ˇ»
15358        }
15359    "});
15360}
15361
15362#[gpui::test]
15363async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15364    init_test(cx, |_| {});
15365    let mut cx = EditorTestContext::new(cx).await;
15366    let language = Arc::new(Language::new(
15367        LanguageConfig {
15368            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15369            ..Default::default()
15370        },
15371        Some(tree_sitter_rust::LANGUAGE.into()),
15372    ));
15373    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15374
15375    let toggle_comments = &ToggleComments {
15376        advance_downwards: false,
15377        ignore_indent: true,
15378    };
15379
15380    // If multiple selections intersect a line, the line is only toggled once.
15381    cx.set_state(indoc! {"
15382        fn a() {
15383        //    «b();
15384        //    c();
15385        //    ˇ» d();
15386        }
15387    "});
15388
15389    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15390
15391    cx.assert_editor_state(indoc! {"
15392        fn a() {
15393            «b();
15394            c();
15395            ˇ» d();
15396        }
15397    "});
15398
15399    // The comment prefix is inserted at the beginning of each line
15400    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15401
15402    cx.assert_editor_state(indoc! {"
15403        fn a() {
15404        //    «b();
15405        //    c();
15406        //    ˇ» d();
15407        }
15408    "});
15409
15410    // If a selection ends at the beginning of a line, that line is not toggled.
15411    cx.set_selections_state(indoc! {"
15412        fn a() {
15413        //    b();
15414        //    «c();
15415        ˇ»//     d();
15416        }
15417    "});
15418
15419    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15420
15421    cx.assert_editor_state(indoc! {"
15422        fn a() {
15423        //    b();
15424            «c();
15425        ˇ»//     d();
15426        }
15427    "});
15428
15429    // If a selection span a single line and is empty, the line is toggled.
15430    cx.set_state(indoc! {"
15431        fn a() {
15432            a();
15433            b();
15434        ˇ
15435        }
15436    "});
15437
15438    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15439
15440    cx.assert_editor_state(indoc! {"
15441        fn a() {
15442            a();
15443            b();
15444        //ˇ
15445        }
15446    "});
15447
15448    // If a selection span multiple lines, empty lines are not toggled.
15449    cx.set_state(indoc! {"
15450        fn a() {
15451            «a();
15452
15453            c();ˇ»
15454        }
15455    "});
15456
15457    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15458
15459    cx.assert_editor_state(indoc! {"
15460        fn a() {
15461        //    «a();
15462
15463        //    c();ˇ»
15464        }
15465    "});
15466
15467    // If a selection includes multiple comment prefixes, all lines are uncommented.
15468    cx.set_state(indoc! {"
15469        fn a() {
15470        //    «a();
15471        ///    b();
15472        //!    c();ˇ»
15473        }
15474    "});
15475
15476    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15477
15478    cx.assert_editor_state(indoc! {"
15479        fn a() {
15480            «a();
15481            b();
15482            c();ˇ»
15483        }
15484    "});
15485}
15486
15487#[gpui::test]
15488async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15489    init_test(cx, |_| {});
15490
15491    let language = Arc::new(Language::new(
15492        LanguageConfig {
15493            line_comments: vec!["// ".into()],
15494            ..Default::default()
15495        },
15496        Some(tree_sitter_rust::LANGUAGE.into()),
15497    ));
15498
15499    let mut cx = EditorTestContext::new(cx).await;
15500
15501    cx.language_registry().add(language.clone());
15502    cx.update_buffer(|buffer, cx| {
15503        buffer.set_language(Some(language), cx);
15504    });
15505
15506    let toggle_comments = &ToggleComments {
15507        advance_downwards: true,
15508        ignore_indent: false,
15509    };
15510
15511    // Single cursor on one line -> advance
15512    // Cursor moves horizontally 3 characters as well on non-blank line
15513    cx.set_state(indoc!(
15514        "fn a() {
15515             ˇdog();
15516             cat();
15517        }"
15518    ));
15519    cx.update_editor(|editor, window, cx| {
15520        editor.toggle_comments(toggle_comments, window, cx);
15521    });
15522    cx.assert_editor_state(indoc!(
15523        "fn a() {
15524             // dog();
15525             catˇ();
15526        }"
15527    ));
15528
15529    // Single selection on one line -> don't advance
15530    cx.set_state(indoc!(
15531        "fn a() {
15532             «dog()ˇ»;
15533             cat();
15534        }"
15535    ));
15536    cx.update_editor(|editor, window, cx| {
15537        editor.toggle_comments(toggle_comments, window, cx);
15538    });
15539    cx.assert_editor_state(indoc!(
15540        "fn a() {
15541             // «dog()ˇ»;
15542             cat();
15543        }"
15544    ));
15545
15546    // Multiple cursors on one line -> advance
15547    cx.set_state(indoc!(
15548        "fn a() {
15549             ˇdˇog();
15550             cat();
15551        }"
15552    ));
15553    cx.update_editor(|editor, window, cx| {
15554        editor.toggle_comments(toggle_comments, window, cx);
15555    });
15556    cx.assert_editor_state(indoc!(
15557        "fn a() {
15558             // dog();
15559             catˇ(ˇ);
15560        }"
15561    ));
15562
15563    // Multiple cursors on one line, with selection -> don't advance
15564    cx.set_state(indoc!(
15565        "fn a() {
15566             ˇdˇog«()ˇ»;
15567             cat();
15568        }"
15569    ));
15570    cx.update_editor(|editor, window, cx| {
15571        editor.toggle_comments(toggle_comments, window, cx);
15572    });
15573    cx.assert_editor_state(indoc!(
15574        "fn a() {
15575             // ˇdˇog«()ˇ»;
15576             cat();
15577        }"
15578    ));
15579
15580    // Single cursor on one line -> advance
15581    // Cursor moves to column 0 on blank line
15582    cx.set_state(indoc!(
15583        "fn a() {
15584             ˇdog();
15585
15586             cat();
15587        }"
15588    ));
15589    cx.update_editor(|editor, window, cx| {
15590        editor.toggle_comments(toggle_comments, window, cx);
15591    });
15592    cx.assert_editor_state(indoc!(
15593        "fn a() {
15594             // dog();
15595        ˇ
15596             cat();
15597        }"
15598    ));
15599
15600    // Single cursor on one line -> advance
15601    // Cursor starts and ends at column 0
15602    cx.set_state(indoc!(
15603        "fn a() {
15604         ˇ    dog();
15605             cat();
15606        }"
15607    ));
15608    cx.update_editor(|editor, window, cx| {
15609        editor.toggle_comments(toggle_comments, window, cx);
15610    });
15611    cx.assert_editor_state(indoc!(
15612        "fn a() {
15613             // dog();
15614         ˇ    cat();
15615        }"
15616    ));
15617}
15618
15619#[gpui::test]
15620async fn test_toggle_block_comment(cx: &mut TestAppContext) {
15621    init_test(cx, |_| {});
15622
15623    let mut cx = EditorTestContext::new(cx).await;
15624
15625    let html_language = Arc::new(
15626        Language::new(
15627            LanguageConfig {
15628                name: "HTML".into(),
15629                block_comment: Some(BlockCommentConfig {
15630                    start: "<!-- ".into(),
15631                    prefix: "".into(),
15632                    end: " -->".into(),
15633                    tab_size: 0,
15634                }),
15635                ..Default::default()
15636            },
15637            Some(tree_sitter_html::LANGUAGE.into()),
15638        )
15639        .with_injection_query(
15640            r#"
15641            (script_element
15642                (raw_text) @injection.content
15643                (#set! injection.language "javascript"))
15644            "#,
15645        )
15646        .unwrap(),
15647    );
15648
15649    let javascript_language = Arc::new(Language::new(
15650        LanguageConfig {
15651            name: "JavaScript".into(),
15652            line_comments: vec!["// ".into()],
15653            ..Default::default()
15654        },
15655        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15656    ));
15657
15658    cx.language_registry().add(html_language.clone());
15659    cx.language_registry().add(javascript_language);
15660    cx.update_buffer(|buffer, cx| {
15661        buffer.set_language(Some(html_language), cx);
15662    });
15663
15664    // Toggle comments for empty selections
15665    cx.set_state(
15666        &r#"
15667            <p>A</p>ˇ
15668            <p>B</p>ˇ
15669            <p>C</p>ˇ
15670        "#
15671        .unindent(),
15672    );
15673    cx.update_editor(|editor, window, cx| {
15674        editor.toggle_comments(&ToggleComments::default(), window, cx)
15675    });
15676    cx.assert_editor_state(
15677        &r#"
15678            <!-- <p>A</p>ˇ -->
15679            <!-- <p>B</p>ˇ -->
15680            <!-- <p>C</p>ˇ -->
15681        "#
15682        .unindent(),
15683    );
15684    cx.update_editor(|editor, window, cx| {
15685        editor.toggle_comments(&ToggleComments::default(), window, cx)
15686    });
15687    cx.assert_editor_state(
15688        &r#"
15689            <p>A</p>ˇ
15690            <p>B</p>ˇ
15691            <p>C</p>ˇ
15692        "#
15693        .unindent(),
15694    );
15695
15696    // Toggle comments for mixture of empty and non-empty selections, where
15697    // multiple selections occupy a given line.
15698    cx.set_state(
15699        &r#"
15700            <p>A«</p>
15701            <p>ˇ»B</p>ˇ
15702            <p>C«</p>
15703            <p>ˇ»D</p>ˇ
15704        "#
15705        .unindent(),
15706    );
15707
15708    cx.update_editor(|editor, window, cx| {
15709        editor.toggle_comments(&ToggleComments::default(), window, cx)
15710    });
15711    cx.assert_editor_state(
15712        &r#"
15713            <!-- <p>A«</p>
15714            <p>ˇ»B</p>ˇ -->
15715            <!-- <p>C«</p>
15716            <p>ˇ»D</p>ˇ -->
15717        "#
15718        .unindent(),
15719    );
15720    cx.update_editor(|editor, window, cx| {
15721        editor.toggle_comments(&ToggleComments::default(), window, cx)
15722    });
15723    cx.assert_editor_state(
15724        &r#"
15725            <p>A«</p>
15726            <p>ˇ»B</p>ˇ
15727            <p>C«</p>
15728            <p>ˇ»D</p>ˇ
15729        "#
15730        .unindent(),
15731    );
15732
15733    // Toggle comments when different languages are active for different
15734    // selections.
15735    cx.set_state(
15736        &r#"
15737            ˇ<script>
15738                ˇvar x = new Y();
15739            ˇ</script>
15740        "#
15741        .unindent(),
15742    );
15743    cx.executor().run_until_parked();
15744    cx.update_editor(|editor, window, cx| {
15745        editor.toggle_comments(&ToggleComments::default(), window, cx)
15746    });
15747    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
15748    // Uncommenting and commenting from this position brings in even more wrong artifacts.
15749    cx.assert_editor_state(
15750        &r#"
15751            <!-- ˇ<script> -->
15752                // ˇvar x = new Y();
15753            <!-- ˇ</script> -->
15754        "#
15755        .unindent(),
15756    );
15757}
15758
15759#[gpui::test]
15760fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
15761    init_test(cx, |_| {});
15762
15763    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15764    let multibuffer = cx.new(|cx| {
15765        let mut multibuffer = MultiBuffer::new(ReadWrite);
15766        multibuffer.push_excerpts(
15767            buffer.clone(),
15768            [
15769                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
15770                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
15771            ],
15772            cx,
15773        );
15774        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
15775        multibuffer
15776    });
15777
15778    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15779    editor.update_in(cx, |editor, window, cx| {
15780        assert_eq!(editor.text(cx), "aaaa\nbbbb");
15781        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15782            s.select_ranges([
15783                Point::new(0, 0)..Point::new(0, 0),
15784                Point::new(1, 0)..Point::new(1, 0),
15785            ])
15786        });
15787
15788        editor.handle_input("X", window, cx);
15789        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
15790        assert_eq!(
15791            editor.selections.ranges(cx),
15792            [
15793                Point::new(0, 1)..Point::new(0, 1),
15794                Point::new(1, 1)..Point::new(1, 1),
15795            ]
15796        );
15797
15798        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
15799        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15800            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
15801        });
15802        editor.backspace(&Default::default(), window, cx);
15803        assert_eq!(editor.text(cx), "Xa\nbbb");
15804        assert_eq!(
15805            editor.selections.ranges(cx),
15806            [Point::new(1, 0)..Point::new(1, 0)]
15807        );
15808
15809        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15810            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
15811        });
15812        editor.backspace(&Default::default(), window, cx);
15813        assert_eq!(editor.text(cx), "X\nbb");
15814        assert_eq!(
15815            editor.selections.ranges(cx),
15816            [Point::new(0, 1)..Point::new(0, 1)]
15817        );
15818    });
15819}
15820
15821#[gpui::test]
15822fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
15823    init_test(cx, |_| {});
15824
15825    let markers = vec![('[', ']').into(), ('(', ')').into()];
15826    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
15827        indoc! {"
15828            [aaaa
15829            (bbbb]
15830            cccc)",
15831        },
15832        markers.clone(),
15833    );
15834    let excerpt_ranges = markers.into_iter().map(|marker| {
15835        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
15836        ExcerptRange::new(context)
15837    });
15838    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
15839    let multibuffer = cx.new(|cx| {
15840        let mut multibuffer = MultiBuffer::new(ReadWrite);
15841        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
15842        multibuffer
15843    });
15844
15845    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15846    editor.update_in(cx, |editor, window, cx| {
15847        let (expected_text, selection_ranges) = marked_text_ranges(
15848            indoc! {"
15849                aaaa
15850                bˇbbb
15851                bˇbbˇb
15852                cccc"
15853            },
15854            true,
15855        );
15856        assert_eq!(editor.text(cx), expected_text);
15857        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15858            s.select_ranges(selection_ranges)
15859        });
15860
15861        editor.handle_input("X", window, cx);
15862
15863        let (expected_text, expected_selections) = marked_text_ranges(
15864            indoc! {"
15865                aaaa
15866                bXˇbbXb
15867                bXˇbbXˇb
15868                cccc"
15869            },
15870            false,
15871        );
15872        assert_eq!(editor.text(cx), expected_text);
15873        assert_eq!(editor.selections.ranges(cx), expected_selections);
15874
15875        editor.newline(&Newline, window, cx);
15876        let (expected_text, expected_selections) = marked_text_ranges(
15877            indoc! {"
15878                aaaa
15879                bX
15880                ˇbbX
15881                b
15882                bX
15883                ˇbbX
15884                ˇb
15885                cccc"
15886            },
15887            false,
15888        );
15889        assert_eq!(editor.text(cx), expected_text);
15890        assert_eq!(editor.selections.ranges(cx), expected_selections);
15891    });
15892}
15893
15894#[gpui::test]
15895fn test_refresh_selections(cx: &mut TestAppContext) {
15896    init_test(cx, |_| {});
15897
15898    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15899    let mut excerpt1_id = None;
15900    let multibuffer = cx.new(|cx| {
15901        let mut multibuffer = MultiBuffer::new(ReadWrite);
15902        excerpt1_id = multibuffer
15903            .push_excerpts(
15904                buffer.clone(),
15905                [
15906                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15907                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15908                ],
15909                cx,
15910            )
15911            .into_iter()
15912            .next();
15913        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15914        multibuffer
15915    });
15916
15917    let editor = cx.add_window(|window, cx| {
15918        let mut editor = build_editor(multibuffer.clone(), window, cx);
15919        let snapshot = editor.snapshot(window, cx);
15920        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15921            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
15922        });
15923        editor.begin_selection(
15924            Point::new(2, 1).to_display_point(&snapshot),
15925            true,
15926            1,
15927            window,
15928            cx,
15929        );
15930        assert_eq!(
15931            editor.selections.ranges(cx),
15932            [
15933                Point::new(1, 3)..Point::new(1, 3),
15934                Point::new(2, 1)..Point::new(2, 1),
15935            ]
15936        );
15937        editor
15938    });
15939
15940    // Refreshing selections is a no-op when excerpts haven't changed.
15941    _ = editor.update(cx, |editor, window, cx| {
15942        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15943        assert_eq!(
15944            editor.selections.ranges(cx),
15945            [
15946                Point::new(1, 3)..Point::new(1, 3),
15947                Point::new(2, 1)..Point::new(2, 1),
15948            ]
15949        );
15950    });
15951
15952    multibuffer.update(cx, |multibuffer, cx| {
15953        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
15954    });
15955    _ = editor.update(cx, |editor, window, cx| {
15956        // Removing an excerpt causes the first selection to become degenerate.
15957        assert_eq!(
15958            editor.selections.ranges(cx),
15959            [
15960                Point::new(0, 0)..Point::new(0, 0),
15961                Point::new(0, 1)..Point::new(0, 1)
15962            ]
15963        );
15964
15965        // Refreshing selections will relocate the first selection to the original buffer
15966        // location.
15967        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15968        assert_eq!(
15969            editor.selections.ranges(cx),
15970            [
15971                Point::new(0, 1)..Point::new(0, 1),
15972                Point::new(0, 3)..Point::new(0, 3)
15973            ]
15974        );
15975        assert!(editor.selections.pending_anchor().is_some());
15976    });
15977}
15978
15979#[gpui::test]
15980fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
15981    init_test(cx, |_| {});
15982
15983    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15984    let mut excerpt1_id = None;
15985    let multibuffer = cx.new(|cx| {
15986        let mut multibuffer = MultiBuffer::new(ReadWrite);
15987        excerpt1_id = multibuffer
15988            .push_excerpts(
15989                buffer.clone(),
15990                [
15991                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15992                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15993                ],
15994                cx,
15995            )
15996            .into_iter()
15997            .next();
15998        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15999        multibuffer
16000    });
16001
16002    let editor = cx.add_window(|window, cx| {
16003        let mut editor = build_editor(multibuffer.clone(), window, cx);
16004        let snapshot = editor.snapshot(window, cx);
16005        editor.begin_selection(
16006            Point::new(1, 3).to_display_point(&snapshot),
16007            false,
16008            1,
16009            window,
16010            cx,
16011        );
16012        assert_eq!(
16013            editor.selections.ranges(cx),
16014            [Point::new(1, 3)..Point::new(1, 3)]
16015        );
16016        editor
16017    });
16018
16019    multibuffer.update(cx, |multibuffer, cx| {
16020        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16021    });
16022    _ = editor.update(cx, |editor, window, cx| {
16023        assert_eq!(
16024            editor.selections.ranges(cx),
16025            [Point::new(0, 0)..Point::new(0, 0)]
16026        );
16027
16028        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
16029        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16030        assert_eq!(
16031            editor.selections.ranges(cx),
16032            [Point::new(0, 3)..Point::new(0, 3)]
16033        );
16034        assert!(editor.selections.pending_anchor().is_some());
16035    });
16036}
16037
16038#[gpui::test]
16039async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
16040    init_test(cx, |_| {});
16041
16042    let language = Arc::new(
16043        Language::new(
16044            LanguageConfig {
16045                brackets: BracketPairConfig {
16046                    pairs: vec![
16047                        BracketPair {
16048                            start: "{".to_string(),
16049                            end: "}".to_string(),
16050                            close: true,
16051                            surround: true,
16052                            newline: true,
16053                        },
16054                        BracketPair {
16055                            start: "/* ".to_string(),
16056                            end: " */".to_string(),
16057                            close: true,
16058                            surround: true,
16059                            newline: true,
16060                        },
16061                    ],
16062                    ..Default::default()
16063                },
16064                ..Default::default()
16065            },
16066            Some(tree_sitter_rust::LANGUAGE.into()),
16067        )
16068        .with_indents_query("")
16069        .unwrap(),
16070    );
16071
16072    let text = concat!(
16073        "{   }\n",     //
16074        "  x\n",       //
16075        "  /*   */\n", //
16076        "x\n",         //
16077        "{{} }\n",     //
16078    );
16079
16080    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16081    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16082    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16083    editor
16084        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16085        .await;
16086
16087    editor.update_in(cx, |editor, window, cx| {
16088        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16089            s.select_display_ranges([
16090                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16091                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16092                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16093            ])
16094        });
16095        editor.newline(&Newline, window, cx);
16096
16097        assert_eq!(
16098            editor.buffer().read(cx).read(cx).text(),
16099            concat!(
16100                "{ \n",    // Suppress rustfmt
16101                "\n",      //
16102                "}\n",     //
16103                "  x\n",   //
16104                "  /* \n", //
16105                "  \n",    //
16106                "  */\n",  //
16107                "x\n",     //
16108                "{{} \n",  //
16109                "}\n",     //
16110            )
16111        );
16112    });
16113}
16114
16115#[gpui::test]
16116fn test_highlighted_ranges(cx: &mut TestAppContext) {
16117    init_test(cx, |_| {});
16118
16119    let editor = cx.add_window(|window, cx| {
16120        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16121        build_editor(buffer, window, cx)
16122    });
16123
16124    _ = editor.update(cx, |editor, window, cx| {
16125        struct Type1;
16126        struct Type2;
16127
16128        let buffer = editor.buffer.read(cx).snapshot(cx);
16129
16130        let anchor_range =
16131            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16132
16133        editor.highlight_background::<Type1>(
16134            &[
16135                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16136                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16137                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16138                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16139            ],
16140            |_| Hsla::red(),
16141            cx,
16142        );
16143        editor.highlight_background::<Type2>(
16144            &[
16145                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16146                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16147                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16148                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16149            ],
16150            |_| Hsla::green(),
16151            cx,
16152        );
16153
16154        let snapshot = editor.snapshot(window, cx);
16155        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16156            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16157            &snapshot,
16158            cx.theme(),
16159        );
16160        assert_eq!(
16161            highlighted_ranges,
16162            &[
16163                (
16164                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16165                    Hsla::green(),
16166                ),
16167                (
16168                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16169                    Hsla::red(),
16170                ),
16171                (
16172                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16173                    Hsla::green(),
16174                ),
16175                (
16176                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16177                    Hsla::red(),
16178                ),
16179            ]
16180        );
16181        assert_eq!(
16182            editor.sorted_background_highlights_in_range(
16183                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16184                &snapshot,
16185                cx.theme(),
16186            ),
16187            &[(
16188                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16189                Hsla::red(),
16190            )]
16191        );
16192    });
16193}
16194
16195#[gpui::test]
16196async fn test_following(cx: &mut TestAppContext) {
16197    init_test(cx, |_| {});
16198
16199    let fs = FakeFs::new(cx.executor());
16200    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16201
16202    let buffer = project.update(cx, |project, cx| {
16203        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16204        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16205    });
16206    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16207    let follower = cx.update(|cx| {
16208        cx.open_window(
16209            WindowOptions {
16210                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16211                    gpui::Point::new(px(0.), px(0.)),
16212                    gpui::Point::new(px(10.), px(80.)),
16213                ))),
16214                ..Default::default()
16215            },
16216            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16217        )
16218        .unwrap()
16219    });
16220
16221    let is_still_following = Rc::new(RefCell::new(true));
16222    let follower_edit_event_count = Rc::new(RefCell::new(0));
16223    let pending_update = Rc::new(RefCell::new(None));
16224    let leader_entity = leader.root(cx).unwrap();
16225    let follower_entity = follower.root(cx).unwrap();
16226    _ = follower.update(cx, {
16227        let update = pending_update.clone();
16228        let is_still_following = is_still_following.clone();
16229        let follower_edit_event_count = follower_edit_event_count.clone();
16230        |_, window, cx| {
16231            cx.subscribe_in(
16232                &leader_entity,
16233                window,
16234                move |_, leader, event, window, cx| {
16235                    leader.read(cx).add_event_to_update_proto(
16236                        event,
16237                        &mut update.borrow_mut(),
16238                        window,
16239                        cx,
16240                    );
16241                },
16242            )
16243            .detach();
16244
16245            cx.subscribe_in(
16246                &follower_entity,
16247                window,
16248                move |_, _, event: &EditorEvent, _window, _cx| {
16249                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16250                        *is_still_following.borrow_mut() = false;
16251                    }
16252
16253                    if let EditorEvent::BufferEdited = event {
16254                        *follower_edit_event_count.borrow_mut() += 1;
16255                    }
16256                },
16257            )
16258            .detach();
16259        }
16260    });
16261
16262    // Update the selections only
16263    _ = leader.update(cx, |leader, window, cx| {
16264        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16265            s.select_ranges([1..1])
16266        });
16267    });
16268    follower
16269        .update(cx, |follower, window, cx| {
16270            follower.apply_update_proto(
16271                &project,
16272                pending_update.borrow_mut().take().unwrap(),
16273                window,
16274                cx,
16275            )
16276        })
16277        .unwrap()
16278        .await
16279        .unwrap();
16280    _ = follower.update(cx, |follower, _, cx| {
16281        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
16282    });
16283    assert!(*is_still_following.borrow());
16284    assert_eq!(*follower_edit_event_count.borrow(), 0);
16285
16286    // Update the scroll position only
16287    _ = leader.update(cx, |leader, window, cx| {
16288        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16289    });
16290    follower
16291        .update(cx, |follower, window, cx| {
16292            follower.apply_update_proto(
16293                &project,
16294                pending_update.borrow_mut().take().unwrap(),
16295                window,
16296                cx,
16297            )
16298        })
16299        .unwrap()
16300        .await
16301        .unwrap();
16302    assert_eq!(
16303        follower
16304            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16305            .unwrap(),
16306        gpui::Point::new(1.5, 3.5)
16307    );
16308    assert!(*is_still_following.borrow());
16309    assert_eq!(*follower_edit_event_count.borrow(), 0);
16310
16311    // Update the selections and scroll position. The follower's scroll position is updated
16312    // via autoscroll, not via the leader's exact scroll position.
16313    _ = leader.update(cx, |leader, window, cx| {
16314        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16315            s.select_ranges([0..0])
16316        });
16317        leader.request_autoscroll(Autoscroll::newest(), cx);
16318        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16319    });
16320    follower
16321        .update(cx, |follower, window, cx| {
16322            follower.apply_update_proto(
16323                &project,
16324                pending_update.borrow_mut().take().unwrap(),
16325                window,
16326                cx,
16327            )
16328        })
16329        .unwrap()
16330        .await
16331        .unwrap();
16332    _ = follower.update(cx, |follower, _, cx| {
16333        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16334        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
16335    });
16336    assert!(*is_still_following.borrow());
16337
16338    // Creating a pending selection that precedes another selection
16339    _ = leader.update(cx, |leader, window, cx| {
16340        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16341            s.select_ranges([1..1])
16342        });
16343        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16344    });
16345    follower
16346        .update(cx, |follower, window, cx| {
16347            follower.apply_update_proto(
16348                &project,
16349                pending_update.borrow_mut().take().unwrap(),
16350                window,
16351                cx,
16352            )
16353        })
16354        .unwrap()
16355        .await
16356        .unwrap();
16357    _ = follower.update(cx, |follower, _, cx| {
16358        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
16359    });
16360    assert!(*is_still_following.borrow());
16361
16362    // Extend the pending selection so that it surrounds another selection
16363    _ = leader.update(cx, |leader, window, cx| {
16364        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16365    });
16366    follower
16367        .update(cx, |follower, window, cx| {
16368            follower.apply_update_proto(
16369                &project,
16370                pending_update.borrow_mut().take().unwrap(),
16371                window,
16372                cx,
16373            )
16374        })
16375        .unwrap()
16376        .await
16377        .unwrap();
16378    _ = follower.update(cx, |follower, _, cx| {
16379        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
16380    });
16381
16382    // Scrolling locally breaks the follow
16383    _ = follower.update(cx, |follower, window, cx| {
16384        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16385        follower.set_scroll_anchor(
16386            ScrollAnchor {
16387                anchor: top_anchor,
16388                offset: gpui::Point::new(0.0, 0.5),
16389            },
16390            window,
16391            cx,
16392        );
16393    });
16394    assert!(!(*is_still_following.borrow()));
16395}
16396
16397#[gpui::test]
16398async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16399    init_test(cx, |_| {});
16400
16401    let fs = FakeFs::new(cx.executor());
16402    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16403    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16404    let pane = workspace
16405        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16406        .unwrap();
16407
16408    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16409
16410    let leader = pane.update_in(cx, |_, window, cx| {
16411        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16412        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16413    });
16414
16415    // Start following the editor when it has no excerpts.
16416    let mut state_message =
16417        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16418    let workspace_entity = workspace.root(cx).unwrap();
16419    let follower_1 = cx
16420        .update_window(*workspace.deref(), |_, window, cx| {
16421            Editor::from_state_proto(
16422                workspace_entity,
16423                ViewId {
16424                    creator: CollaboratorId::PeerId(PeerId::default()),
16425                    id: 0,
16426                },
16427                &mut state_message,
16428                window,
16429                cx,
16430            )
16431        })
16432        .unwrap()
16433        .unwrap()
16434        .await
16435        .unwrap();
16436
16437    let update_message = Rc::new(RefCell::new(None));
16438    follower_1.update_in(cx, {
16439        let update = update_message.clone();
16440        |_, window, cx| {
16441            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16442                leader.read(cx).add_event_to_update_proto(
16443                    event,
16444                    &mut update.borrow_mut(),
16445                    window,
16446                    cx,
16447                );
16448            })
16449            .detach();
16450        }
16451    });
16452
16453    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16454        (
16455            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16456            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16457        )
16458    });
16459
16460    // Insert some excerpts.
16461    leader.update(cx, |leader, cx| {
16462        leader.buffer.update(cx, |multibuffer, cx| {
16463            multibuffer.set_excerpts_for_path(
16464                PathKey::namespaced(1, "b.txt".into()),
16465                buffer_1.clone(),
16466                vec![
16467                    Point::row_range(0..3),
16468                    Point::row_range(1..6),
16469                    Point::row_range(12..15),
16470                ],
16471                0,
16472                cx,
16473            );
16474            multibuffer.set_excerpts_for_path(
16475                PathKey::namespaced(1, "a.txt".into()),
16476                buffer_2.clone(),
16477                vec![Point::row_range(0..6), Point::row_range(8..12)],
16478                0,
16479                cx,
16480            );
16481        });
16482    });
16483
16484    // Apply the update of adding the excerpts.
16485    follower_1
16486        .update_in(cx, |follower, window, cx| {
16487            follower.apply_update_proto(
16488                &project,
16489                update_message.borrow().clone().unwrap(),
16490                window,
16491                cx,
16492            )
16493        })
16494        .await
16495        .unwrap();
16496    assert_eq!(
16497        follower_1.update(cx, |editor, cx| editor.text(cx)),
16498        leader.update(cx, |editor, cx| editor.text(cx))
16499    );
16500    update_message.borrow_mut().take();
16501
16502    // Start following separately after it already has excerpts.
16503    let mut state_message =
16504        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16505    let workspace_entity = workspace.root(cx).unwrap();
16506    let follower_2 = cx
16507        .update_window(*workspace.deref(), |_, window, cx| {
16508            Editor::from_state_proto(
16509                workspace_entity,
16510                ViewId {
16511                    creator: CollaboratorId::PeerId(PeerId::default()),
16512                    id: 0,
16513                },
16514                &mut state_message,
16515                window,
16516                cx,
16517            )
16518        })
16519        .unwrap()
16520        .unwrap()
16521        .await
16522        .unwrap();
16523    assert_eq!(
16524        follower_2.update(cx, |editor, cx| editor.text(cx)),
16525        leader.update(cx, |editor, cx| editor.text(cx))
16526    );
16527
16528    // Remove some excerpts.
16529    leader.update(cx, |leader, cx| {
16530        leader.buffer.update(cx, |multibuffer, cx| {
16531            let excerpt_ids = multibuffer.excerpt_ids();
16532            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16533            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16534        });
16535    });
16536
16537    // Apply the update of removing the excerpts.
16538    follower_1
16539        .update_in(cx, |follower, window, cx| {
16540            follower.apply_update_proto(
16541                &project,
16542                update_message.borrow().clone().unwrap(),
16543                window,
16544                cx,
16545            )
16546        })
16547        .await
16548        .unwrap();
16549    follower_2
16550        .update_in(cx, |follower, window, cx| {
16551            follower.apply_update_proto(
16552                &project,
16553                update_message.borrow().clone().unwrap(),
16554                window,
16555                cx,
16556            )
16557        })
16558        .await
16559        .unwrap();
16560    update_message.borrow_mut().take();
16561    assert_eq!(
16562        follower_1.update(cx, |editor, cx| editor.text(cx)),
16563        leader.update(cx, |editor, cx| editor.text(cx))
16564    );
16565}
16566
16567#[gpui::test]
16568async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16569    init_test(cx, |_| {});
16570
16571    let mut cx = EditorTestContext::new(cx).await;
16572    let lsp_store =
16573        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
16574
16575    cx.set_state(indoc! {"
16576        ˇfn func(abc def: i32) -> u32 {
16577        }
16578    "});
16579
16580    cx.update(|_, cx| {
16581        lsp_store.update(cx, |lsp_store, cx| {
16582            lsp_store
16583                .update_diagnostics(
16584                    LanguageServerId(0),
16585                    lsp::PublishDiagnosticsParams {
16586                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
16587                        version: None,
16588                        diagnostics: vec![
16589                            lsp::Diagnostic {
16590                                range: lsp::Range::new(
16591                                    lsp::Position::new(0, 11),
16592                                    lsp::Position::new(0, 12),
16593                                ),
16594                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16595                                ..Default::default()
16596                            },
16597                            lsp::Diagnostic {
16598                                range: lsp::Range::new(
16599                                    lsp::Position::new(0, 12),
16600                                    lsp::Position::new(0, 15),
16601                                ),
16602                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16603                                ..Default::default()
16604                            },
16605                            lsp::Diagnostic {
16606                                range: lsp::Range::new(
16607                                    lsp::Position::new(0, 25),
16608                                    lsp::Position::new(0, 28),
16609                                ),
16610                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16611                                ..Default::default()
16612                            },
16613                        ],
16614                    },
16615                    None,
16616                    DiagnosticSourceKind::Pushed,
16617                    &[],
16618                    cx,
16619                )
16620                .unwrap()
16621        });
16622    });
16623
16624    executor.run_until_parked();
16625
16626    cx.update_editor(|editor, window, cx| {
16627        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16628    });
16629
16630    cx.assert_editor_state(indoc! {"
16631        fn func(abc def: i32) -> ˇu32 {
16632        }
16633    "});
16634
16635    cx.update_editor(|editor, window, cx| {
16636        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16637    });
16638
16639    cx.assert_editor_state(indoc! {"
16640        fn func(abc ˇdef: i32) -> u32 {
16641        }
16642    "});
16643
16644    cx.update_editor(|editor, window, cx| {
16645        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16646    });
16647
16648    cx.assert_editor_state(indoc! {"
16649        fn func(abcˇ def: i32) -> u32 {
16650        }
16651    "});
16652
16653    cx.update_editor(|editor, window, cx| {
16654        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16655    });
16656
16657    cx.assert_editor_state(indoc! {"
16658        fn func(abc def: i32) -> ˇu32 {
16659        }
16660    "});
16661}
16662
16663#[gpui::test]
16664async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16665    init_test(cx, |_| {});
16666
16667    let mut cx = EditorTestContext::new(cx).await;
16668
16669    let diff_base = r#"
16670        use some::mod;
16671
16672        const A: u32 = 42;
16673
16674        fn main() {
16675            println!("hello");
16676
16677            println!("world");
16678        }
16679        "#
16680    .unindent();
16681
16682    // Edits are modified, removed, modified, added
16683    cx.set_state(
16684        &r#"
16685        use some::modified;
16686
16687        ˇ
16688        fn main() {
16689            println!("hello there");
16690
16691            println!("around the");
16692            println!("world");
16693        }
16694        "#
16695        .unindent(),
16696    );
16697
16698    cx.set_head_text(&diff_base);
16699    executor.run_until_parked();
16700
16701    cx.update_editor(|editor, window, cx| {
16702        //Wrap around the bottom of the buffer
16703        for _ in 0..3 {
16704            editor.go_to_next_hunk(&GoToHunk, window, cx);
16705        }
16706    });
16707
16708    cx.assert_editor_state(
16709        &r#"
16710        ˇuse some::modified;
16711
16712
16713        fn main() {
16714            println!("hello there");
16715
16716            println!("around the");
16717            println!("world");
16718        }
16719        "#
16720        .unindent(),
16721    );
16722
16723    cx.update_editor(|editor, window, cx| {
16724        //Wrap around the top of the buffer
16725        for _ in 0..2 {
16726            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16727        }
16728    });
16729
16730    cx.assert_editor_state(
16731        &r#"
16732        use some::modified;
16733
16734
16735        fn main() {
16736        ˇ    println!("hello there");
16737
16738            println!("around the");
16739            println!("world");
16740        }
16741        "#
16742        .unindent(),
16743    );
16744
16745    cx.update_editor(|editor, window, cx| {
16746        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16747    });
16748
16749    cx.assert_editor_state(
16750        &r#"
16751        use some::modified;
16752
16753        ˇ
16754        fn main() {
16755            println!("hello there");
16756
16757            println!("around the");
16758            println!("world");
16759        }
16760        "#
16761        .unindent(),
16762    );
16763
16764    cx.update_editor(|editor, window, cx| {
16765        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16766    });
16767
16768    cx.assert_editor_state(
16769        &r#"
16770        ˇuse some::modified;
16771
16772
16773        fn main() {
16774            println!("hello there");
16775
16776            println!("around the");
16777            println!("world");
16778        }
16779        "#
16780        .unindent(),
16781    );
16782
16783    cx.update_editor(|editor, window, cx| {
16784        for _ in 0..2 {
16785            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16786        }
16787    });
16788
16789    cx.assert_editor_state(
16790        &r#"
16791        use some::modified;
16792
16793
16794        fn main() {
16795        ˇ    println!("hello there");
16796
16797            println!("around the");
16798            println!("world");
16799        }
16800        "#
16801        .unindent(),
16802    );
16803
16804    cx.update_editor(|editor, window, cx| {
16805        editor.fold(&Fold, window, cx);
16806    });
16807
16808    cx.update_editor(|editor, window, cx| {
16809        editor.go_to_next_hunk(&GoToHunk, window, cx);
16810    });
16811
16812    cx.assert_editor_state(
16813        &r#"
16814        ˇuse some::modified;
16815
16816
16817        fn main() {
16818            println!("hello there");
16819
16820            println!("around the");
16821            println!("world");
16822        }
16823        "#
16824        .unindent(),
16825    );
16826}
16827
16828#[test]
16829fn test_split_words() {
16830    fn split(text: &str) -> Vec<&str> {
16831        split_words(text).collect()
16832    }
16833
16834    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
16835    assert_eq!(split("hello_world"), &["hello_", "world"]);
16836    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
16837    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
16838    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
16839    assert_eq!(split("helloworld"), &["helloworld"]);
16840
16841    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
16842}
16843
16844#[gpui::test]
16845async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
16846    init_test(cx, |_| {});
16847
16848    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
16849    let mut assert = |before, after| {
16850        let _state_context = cx.set_state(before);
16851        cx.run_until_parked();
16852        cx.update_editor(|editor, window, cx| {
16853            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
16854        });
16855        cx.run_until_parked();
16856        cx.assert_editor_state(after);
16857    };
16858
16859    // Outside bracket jumps to outside of matching bracket
16860    assert("console.logˇ(var);", "console.log(var)ˇ;");
16861    assert("console.log(var)ˇ;", "console.logˇ(var);");
16862
16863    // Inside bracket jumps to inside of matching bracket
16864    assert("console.log(ˇvar);", "console.log(varˇ);");
16865    assert("console.log(varˇ);", "console.log(ˇvar);");
16866
16867    // When outside a bracket and inside, favor jumping to the inside bracket
16868    assert(
16869        "console.log('foo', [1, 2, 3]ˇ);",
16870        "console.log(ˇ'foo', [1, 2, 3]);",
16871    );
16872    assert(
16873        "console.log(ˇ'foo', [1, 2, 3]);",
16874        "console.log('foo', [1, 2, 3]ˇ);",
16875    );
16876
16877    // Bias forward if two options are equally likely
16878    assert(
16879        "let result = curried_fun()ˇ();",
16880        "let result = curried_fun()()ˇ;",
16881    );
16882
16883    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
16884    assert(
16885        indoc! {"
16886            function test() {
16887                console.log('test')ˇ
16888            }"},
16889        indoc! {"
16890            function test() {
16891                console.logˇ('test')
16892            }"},
16893    );
16894}
16895
16896#[gpui::test]
16897async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
16898    init_test(cx, |_| {});
16899
16900    let fs = FakeFs::new(cx.executor());
16901    fs.insert_tree(
16902        path!("/a"),
16903        json!({
16904            "main.rs": "fn main() { let a = 5; }",
16905            "other.rs": "// Test file",
16906        }),
16907    )
16908    .await;
16909    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16910
16911    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16912    language_registry.add(Arc::new(Language::new(
16913        LanguageConfig {
16914            name: "Rust".into(),
16915            matcher: LanguageMatcher {
16916                path_suffixes: vec!["rs".to_string()],
16917                ..Default::default()
16918            },
16919            brackets: BracketPairConfig {
16920                pairs: vec![BracketPair {
16921                    start: "{".to_string(),
16922                    end: "}".to_string(),
16923                    close: true,
16924                    surround: true,
16925                    newline: true,
16926                }],
16927                disabled_scopes_by_bracket_ix: Vec::new(),
16928            },
16929            ..Default::default()
16930        },
16931        Some(tree_sitter_rust::LANGUAGE.into()),
16932    )));
16933    let mut fake_servers = language_registry.register_fake_lsp(
16934        "Rust",
16935        FakeLspAdapter {
16936            capabilities: lsp::ServerCapabilities {
16937                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
16938                    first_trigger_character: "{".to_string(),
16939                    more_trigger_character: None,
16940                }),
16941                ..Default::default()
16942            },
16943            ..Default::default()
16944        },
16945    );
16946
16947    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16948
16949    let cx = &mut VisualTestContext::from_window(*workspace, cx);
16950
16951    let worktree_id = workspace
16952        .update(cx, |workspace, _, cx| {
16953            workspace.project().update(cx, |project, cx| {
16954                project.worktrees(cx).next().unwrap().read(cx).id()
16955            })
16956        })
16957        .unwrap();
16958
16959    let buffer = project
16960        .update(cx, |project, cx| {
16961            project.open_local_buffer(path!("/a/main.rs"), cx)
16962        })
16963        .await
16964        .unwrap();
16965    let editor_handle = workspace
16966        .update(cx, |workspace, window, cx| {
16967            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
16968        })
16969        .unwrap()
16970        .await
16971        .unwrap()
16972        .downcast::<Editor>()
16973        .unwrap();
16974
16975    cx.executor().start_waiting();
16976    let fake_server = fake_servers.next().await.unwrap();
16977
16978    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
16979        |params, _| async move {
16980            assert_eq!(
16981                params.text_document_position.text_document.uri,
16982                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
16983            );
16984            assert_eq!(
16985                params.text_document_position.position,
16986                lsp::Position::new(0, 21),
16987            );
16988
16989            Ok(Some(vec![lsp::TextEdit {
16990                new_text: "]".to_string(),
16991                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16992            }]))
16993        },
16994    );
16995
16996    editor_handle.update_in(cx, |editor, window, cx| {
16997        window.focus(&editor.focus_handle(cx));
16998        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16999            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
17000        });
17001        editor.handle_input("{", window, cx);
17002    });
17003
17004    cx.executor().run_until_parked();
17005
17006    buffer.update(cx, |buffer, _| {
17007        assert_eq!(
17008            buffer.text(),
17009            "fn main() { let a = {5}; }",
17010            "No extra braces from on type formatting should appear in the buffer"
17011        )
17012    });
17013}
17014
17015#[gpui::test(iterations = 20, seeds(31))]
17016async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
17017    init_test(cx, |_| {});
17018
17019    let mut cx = EditorLspTestContext::new_rust(
17020        lsp::ServerCapabilities {
17021            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17022                first_trigger_character: ".".to_string(),
17023                more_trigger_character: None,
17024            }),
17025            ..Default::default()
17026        },
17027        cx,
17028    )
17029    .await;
17030
17031    cx.update_buffer(|buffer, _| {
17032        // This causes autoindent to be async.
17033        buffer.set_sync_parse_timeout(Duration::ZERO)
17034    });
17035
17036    cx.set_state("fn c() {\n    d()ˇ\n}\n");
17037    cx.simulate_keystroke("\n");
17038    cx.run_until_parked();
17039
17040    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
17041    let mut request =
17042        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
17043            let buffer_cloned = buffer_cloned.clone();
17044            async move {
17045                buffer_cloned.update(&mut cx, |buffer, _| {
17046                    assert_eq!(
17047                        buffer.text(),
17048                        "fn c() {\n    d()\n        .\n}\n",
17049                        "OnTypeFormatting should triggered after autoindent applied"
17050                    )
17051                })?;
17052
17053                Ok(Some(vec![]))
17054            }
17055        });
17056
17057    cx.simulate_keystroke(".");
17058    cx.run_until_parked();
17059
17060    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
17061    assert!(request.next().await.is_some());
17062    request.close();
17063    assert!(request.next().await.is_none());
17064}
17065
17066#[gpui::test]
17067async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17068    init_test(cx, |_| {});
17069
17070    let fs = FakeFs::new(cx.executor());
17071    fs.insert_tree(
17072        path!("/a"),
17073        json!({
17074            "main.rs": "fn main() { let a = 5; }",
17075            "other.rs": "// Test file",
17076        }),
17077    )
17078    .await;
17079
17080    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17081
17082    let server_restarts = Arc::new(AtomicUsize::new(0));
17083    let closure_restarts = Arc::clone(&server_restarts);
17084    let language_server_name = "test language server";
17085    let language_name: LanguageName = "Rust".into();
17086
17087    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17088    language_registry.add(Arc::new(Language::new(
17089        LanguageConfig {
17090            name: language_name.clone(),
17091            matcher: LanguageMatcher {
17092                path_suffixes: vec!["rs".to_string()],
17093                ..Default::default()
17094            },
17095            ..Default::default()
17096        },
17097        Some(tree_sitter_rust::LANGUAGE.into()),
17098    )));
17099    let mut fake_servers = language_registry.register_fake_lsp(
17100        "Rust",
17101        FakeLspAdapter {
17102            name: language_server_name,
17103            initialization_options: Some(json!({
17104                "testOptionValue": true
17105            })),
17106            initializer: Some(Box::new(move |fake_server| {
17107                let task_restarts = Arc::clone(&closure_restarts);
17108                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17109                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17110                    futures::future::ready(Ok(()))
17111                });
17112            })),
17113            ..Default::default()
17114        },
17115    );
17116
17117    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17118    let _buffer = project
17119        .update(cx, |project, cx| {
17120            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17121        })
17122        .await
17123        .unwrap();
17124    let _fake_server = fake_servers.next().await.unwrap();
17125    update_test_language_settings(cx, |language_settings| {
17126        language_settings.languages.0.insert(
17127            language_name.clone().0,
17128            LanguageSettingsContent {
17129                tab_size: NonZeroU32::new(8),
17130                ..Default::default()
17131            },
17132        );
17133    });
17134    cx.executor().run_until_parked();
17135    assert_eq!(
17136        server_restarts.load(atomic::Ordering::Acquire),
17137        0,
17138        "Should not restart LSP server on an unrelated change"
17139    );
17140
17141    update_test_project_settings(cx, |project_settings| {
17142        project_settings.lsp.insert(
17143            "Some other server name".into(),
17144            LspSettings {
17145                binary: None,
17146                settings: None,
17147                initialization_options: Some(json!({
17148                    "some other init value": false
17149                })),
17150                enable_lsp_tasks: false,
17151                fetch: None,
17152            },
17153        );
17154    });
17155    cx.executor().run_until_parked();
17156    assert_eq!(
17157        server_restarts.load(atomic::Ordering::Acquire),
17158        0,
17159        "Should not restart LSP server on an unrelated LSP settings change"
17160    );
17161
17162    update_test_project_settings(cx, |project_settings| {
17163        project_settings.lsp.insert(
17164            language_server_name.into(),
17165            LspSettings {
17166                binary: None,
17167                settings: None,
17168                initialization_options: Some(json!({
17169                    "anotherInitValue": false
17170                })),
17171                enable_lsp_tasks: false,
17172                fetch: None,
17173            },
17174        );
17175    });
17176    cx.executor().run_until_parked();
17177    assert_eq!(
17178        server_restarts.load(atomic::Ordering::Acquire),
17179        1,
17180        "Should restart LSP server on a related LSP settings change"
17181    );
17182
17183    update_test_project_settings(cx, |project_settings| {
17184        project_settings.lsp.insert(
17185            language_server_name.into(),
17186            LspSettings {
17187                binary: None,
17188                settings: None,
17189                initialization_options: Some(json!({
17190                    "anotherInitValue": false
17191                })),
17192                enable_lsp_tasks: false,
17193                fetch: None,
17194            },
17195        );
17196    });
17197    cx.executor().run_until_parked();
17198    assert_eq!(
17199        server_restarts.load(atomic::Ordering::Acquire),
17200        1,
17201        "Should not restart LSP server on a related LSP settings change that is the same"
17202    );
17203
17204    update_test_project_settings(cx, |project_settings| {
17205        project_settings.lsp.insert(
17206            language_server_name.into(),
17207            LspSettings {
17208                binary: None,
17209                settings: None,
17210                initialization_options: None,
17211                enable_lsp_tasks: false,
17212                fetch: None,
17213            },
17214        );
17215    });
17216    cx.executor().run_until_parked();
17217    assert_eq!(
17218        server_restarts.load(atomic::Ordering::Acquire),
17219        2,
17220        "Should restart LSP server on another related LSP settings change"
17221    );
17222}
17223
17224#[gpui::test]
17225async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17226    init_test(cx, |_| {});
17227
17228    let mut cx = EditorLspTestContext::new_rust(
17229        lsp::ServerCapabilities {
17230            completion_provider: Some(lsp::CompletionOptions {
17231                trigger_characters: Some(vec![".".to_string()]),
17232                resolve_provider: Some(true),
17233                ..Default::default()
17234            }),
17235            ..Default::default()
17236        },
17237        cx,
17238    )
17239    .await;
17240
17241    cx.set_state("fn main() { let a = 2ˇ; }");
17242    cx.simulate_keystroke(".");
17243    let completion_item = lsp::CompletionItem {
17244        label: "some".into(),
17245        kind: Some(lsp::CompletionItemKind::SNIPPET),
17246        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17247        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17248            kind: lsp::MarkupKind::Markdown,
17249            value: "```rust\nSome(2)\n```".to_string(),
17250        })),
17251        deprecated: Some(false),
17252        sort_text: Some("fffffff2".to_string()),
17253        filter_text: Some("some".to_string()),
17254        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17255        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17256            range: lsp::Range {
17257                start: lsp::Position {
17258                    line: 0,
17259                    character: 22,
17260                },
17261                end: lsp::Position {
17262                    line: 0,
17263                    character: 22,
17264                },
17265            },
17266            new_text: "Some(2)".to_string(),
17267        })),
17268        additional_text_edits: Some(vec![lsp::TextEdit {
17269            range: lsp::Range {
17270                start: lsp::Position {
17271                    line: 0,
17272                    character: 20,
17273                },
17274                end: lsp::Position {
17275                    line: 0,
17276                    character: 22,
17277                },
17278            },
17279            new_text: "".to_string(),
17280        }]),
17281        ..Default::default()
17282    };
17283
17284    let closure_completion_item = completion_item.clone();
17285    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17286        let task_completion_item = closure_completion_item.clone();
17287        async move {
17288            Ok(Some(lsp::CompletionResponse::Array(vec![
17289                task_completion_item,
17290            ])))
17291        }
17292    });
17293
17294    request.next().await;
17295
17296    cx.condition(|editor, _| editor.context_menu_visible())
17297        .await;
17298    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17299        editor
17300            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17301            .unwrap()
17302    });
17303    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17304
17305    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17306        let task_completion_item = completion_item.clone();
17307        async move { Ok(task_completion_item) }
17308    })
17309    .next()
17310    .await
17311    .unwrap();
17312    apply_additional_edits.await.unwrap();
17313    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17314}
17315
17316#[gpui::test]
17317async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17318    init_test(cx, |_| {});
17319
17320    let mut cx = EditorLspTestContext::new_rust(
17321        lsp::ServerCapabilities {
17322            completion_provider: Some(lsp::CompletionOptions {
17323                trigger_characters: Some(vec![".".to_string()]),
17324                resolve_provider: Some(true),
17325                ..Default::default()
17326            }),
17327            ..Default::default()
17328        },
17329        cx,
17330    )
17331    .await;
17332
17333    cx.set_state("fn main() { let a = 2ˇ; }");
17334    cx.simulate_keystroke(".");
17335
17336    let item1 = lsp::CompletionItem {
17337        label: "method id()".to_string(),
17338        filter_text: Some("id".to_string()),
17339        detail: None,
17340        documentation: None,
17341        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17342            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17343            new_text: ".id".to_string(),
17344        })),
17345        ..lsp::CompletionItem::default()
17346    };
17347
17348    let item2 = lsp::CompletionItem {
17349        label: "other".to_string(),
17350        filter_text: Some("other".to_string()),
17351        detail: None,
17352        documentation: None,
17353        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17354            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17355            new_text: ".other".to_string(),
17356        })),
17357        ..lsp::CompletionItem::default()
17358    };
17359
17360    let item1 = item1.clone();
17361    cx.set_request_handler::<lsp::request::Completion, _, _>({
17362        let item1 = item1.clone();
17363        move |_, _, _| {
17364            let item1 = item1.clone();
17365            let item2 = item2.clone();
17366            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17367        }
17368    })
17369    .next()
17370    .await;
17371
17372    cx.condition(|editor, _| editor.context_menu_visible())
17373        .await;
17374    cx.update_editor(|editor, _, _| {
17375        let context_menu = editor.context_menu.borrow_mut();
17376        let context_menu = context_menu
17377            .as_ref()
17378            .expect("Should have the context menu deployed");
17379        match context_menu {
17380            CodeContextMenu::Completions(completions_menu) => {
17381                let completions = completions_menu.completions.borrow_mut();
17382                assert_eq!(
17383                    completions
17384                        .iter()
17385                        .map(|completion| &completion.label.text)
17386                        .collect::<Vec<_>>(),
17387                    vec!["method id()", "other"]
17388                )
17389            }
17390            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17391        }
17392    });
17393
17394    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17395        let item1 = item1.clone();
17396        move |_, item_to_resolve, _| {
17397            let item1 = item1.clone();
17398            async move {
17399                if item1 == item_to_resolve {
17400                    Ok(lsp::CompletionItem {
17401                        label: "method id()".to_string(),
17402                        filter_text: Some("id".to_string()),
17403                        detail: Some("Now resolved!".to_string()),
17404                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17405                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17406                            range: lsp::Range::new(
17407                                lsp::Position::new(0, 22),
17408                                lsp::Position::new(0, 22),
17409                            ),
17410                            new_text: ".id".to_string(),
17411                        })),
17412                        ..lsp::CompletionItem::default()
17413                    })
17414                } else {
17415                    Ok(item_to_resolve)
17416                }
17417            }
17418        }
17419    })
17420    .next()
17421    .await
17422    .unwrap();
17423    cx.run_until_parked();
17424
17425    cx.update_editor(|editor, window, cx| {
17426        editor.context_menu_next(&Default::default(), window, cx);
17427    });
17428
17429    cx.update_editor(|editor, _, _| {
17430        let context_menu = editor.context_menu.borrow_mut();
17431        let context_menu = context_menu
17432            .as_ref()
17433            .expect("Should have the context menu deployed");
17434        match context_menu {
17435            CodeContextMenu::Completions(completions_menu) => {
17436                let completions = completions_menu.completions.borrow_mut();
17437                assert_eq!(
17438                    completions
17439                        .iter()
17440                        .map(|completion| &completion.label.text)
17441                        .collect::<Vec<_>>(),
17442                    vec!["method id() Now resolved!", "other"],
17443                    "Should update first completion label, but not second as the filter text did not match."
17444                );
17445            }
17446            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17447        }
17448    });
17449}
17450
17451#[gpui::test]
17452async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17453    init_test(cx, |_| {});
17454    let mut cx = EditorLspTestContext::new_rust(
17455        lsp::ServerCapabilities {
17456            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17457            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17458            completion_provider: Some(lsp::CompletionOptions {
17459                resolve_provider: Some(true),
17460                ..Default::default()
17461            }),
17462            ..Default::default()
17463        },
17464        cx,
17465    )
17466    .await;
17467    cx.set_state(indoc! {"
17468        struct TestStruct {
17469            field: i32
17470        }
17471
17472        fn mainˇ() {
17473            let unused_var = 42;
17474            let test_struct = TestStruct { field: 42 };
17475        }
17476    "});
17477    let symbol_range = cx.lsp_range(indoc! {"
17478        struct TestStruct {
17479            field: i32
17480        }
17481
17482        «fn main»() {
17483            let unused_var = 42;
17484            let test_struct = TestStruct { field: 42 };
17485        }
17486    "});
17487    let mut hover_requests =
17488        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17489            Ok(Some(lsp::Hover {
17490                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17491                    kind: lsp::MarkupKind::Markdown,
17492                    value: "Function documentation".to_string(),
17493                }),
17494                range: Some(symbol_range),
17495            }))
17496        });
17497
17498    // Case 1: Test that code action menu hide hover popover
17499    cx.dispatch_action(Hover);
17500    hover_requests.next().await;
17501    cx.condition(|editor, _| editor.hover_state.visible()).await;
17502    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17503        move |_, _, _| async move {
17504            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17505                lsp::CodeAction {
17506                    title: "Remove unused variable".to_string(),
17507                    kind: Some(CodeActionKind::QUICKFIX),
17508                    edit: Some(lsp::WorkspaceEdit {
17509                        changes: Some(
17510                            [(
17511                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17512                                vec![lsp::TextEdit {
17513                                    range: lsp::Range::new(
17514                                        lsp::Position::new(5, 4),
17515                                        lsp::Position::new(5, 27),
17516                                    ),
17517                                    new_text: "".to_string(),
17518                                }],
17519                            )]
17520                            .into_iter()
17521                            .collect(),
17522                        ),
17523                        ..Default::default()
17524                    }),
17525                    ..Default::default()
17526                },
17527            )]))
17528        },
17529    );
17530    cx.update_editor(|editor, window, cx| {
17531        editor.toggle_code_actions(
17532            &ToggleCodeActions {
17533                deployed_from: None,
17534                quick_launch: false,
17535            },
17536            window,
17537            cx,
17538        );
17539    });
17540    code_action_requests.next().await;
17541    cx.run_until_parked();
17542    cx.condition(|editor, _| editor.context_menu_visible())
17543        .await;
17544    cx.update_editor(|editor, _, _| {
17545        assert!(
17546            !editor.hover_state.visible(),
17547            "Hover popover should be hidden when code action menu is shown"
17548        );
17549        // Hide code actions
17550        editor.context_menu.take();
17551    });
17552
17553    // Case 2: Test that code completions hide hover popover
17554    cx.dispatch_action(Hover);
17555    hover_requests.next().await;
17556    cx.condition(|editor, _| editor.hover_state.visible()).await;
17557    let counter = Arc::new(AtomicUsize::new(0));
17558    let mut completion_requests =
17559        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17560            let counter = counter.clone();
17561            async move {
17562                counter.fetch_add(1, atomic::Ordering::Release);
17563                Ok(Some(lsp::CompletionResponse::Array(vec![
17564                    lsp::CompletionItem {
17565                        label: "main".into(),
17566                        kind: Some(lsp::CompletionItemKind::FUNCTION),
17567                        detail: Some("() -> ()".to_string()),
17568                        ..Default::default()
17569                    },
17570                    lsp::CompletionItem {
17571                        label: "TestStruct".into(),
17572                        kind: Some(lsp::CompletionItemKind::STRUCT),
17573                        detail: Some("struct TestStruct".to_string()),
17574                        ..Default::default()
17575                    },
17576                ])))
17577            }
17578        });
17579    cx.update_editor(|editor, window, cx| {
17580        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
17581    });
17582    completion_requests.next().await;
17583    cx.condition(|editor, _| editor.context_menu_visible())
17584        .await;
17585    cx.update_editor(|editor, _, _| {
17586        assert!(
17587            !editor.hover_state.visible(),
17588            "Hover popover should be hidden when completion menu is shown"
17589        );
17590    });
17591}
17592
17593#[gpui::test]
17594async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
17595    init_test(cx, |_| {});
17596
17597    let mut cx = EditorLspTestContext::new_rust(
17598        lsp::ServerCapabilities {
17599            completion_provider: Some(lsp::CompletionOptions {
17600                trigger_characters: Some(vec![".".to_string()]),
17601                resolve_provider: Some(true),
17602                ..Default::default()
17603            }),
17604            ..Default::default()
17605        },
17606        cx,
17607    )
17608    .await;
17609
17610    cx.set_state("fn main() { let a = 2ˇ; }");
17611    cx.simulate_keystroke(".");
17612
17613    let unresolved_item_1 = lsp::CompletionItem {
17614        label: "id".to_string(),
17615        filter_text: Some("id".to_string()),
17616        detail: None,
17617        documentation: None,
17618        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17619            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17620            new_text: ".id".to_string(),
17621        })),
17622        ..lsp::CompletionItem::default()
17623    };
17624    let resolved_item_1 = lsp::CompletionItem {
17625        additional_text_edits: Some(vec![lsp::TextEdit {
17626            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17627            new_text: "!!".to_string(),
17628        }]),
17629        ..unresolved_item_1.clone()
17630    };
17631    let unresolved_item_2 = lsp::CompletionItem {
17632        label: "other".to_string(),
17633        filter_text: Some("other".to_string()),
17634        detail: None,
17635        documentation: None,
17636        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17637            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17638            new_text: ".other".to_string(),
17639        })),
17640        ..lsp::CompletionItem::default()
17641    };
17642    let resolved_item_2 = lsp::CompletionItem {
17643        additional_text_edits: Some(vec![lsp::TextEdit {
17644            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17645            new_text: "??".to_string(),
17646        }]),
17647        ..unresolved_item_2.clone()
17648    };
17649
17650    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
17651    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
17652    cx.lsp
17653        .server
17654        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17655            let unresolved_item_1 = unresolved_item_1.clone();
17656            let resolved_item_1 = resolved_item_1.clone();
17657            let unresolved_item_2 = unresolved_item_2.clone();
17658            let resolved_item_2 = resolved_item_2.clone();
17659            let resolve_requests_1 = resolve_requests_1.clone();
17660            let resolve_requests_2 = resolve_requests_2.clone();
17661            move |unresolved_request, _| {
17662                let unresolved_item_1 = unresolved_item_1.clone();
17663                let resolved_item_1 = resolved_item_1.clone();
17664                let unresolved_item_2 = unresolved_item_2.clone();
17665                let resolved_item_2 = resolved_item_2.clone();
17666                let resolve_requests_1 = resolve_requests_1.clone();
17667                let resolve_requests_2 = resolve_requests_2.clone();
17668                async move {
17669                    if unresolved_request == unresolved_item_1 {
17670                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
17671                        Ok(resolved_item_1.clone())
17672                    } else if unresolved_request == unresolved_item_2 {
17673                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
17674                        Ok(resolved_item_2.clone())
17675                    } else {
17676                        panic!("Unexpected completion item {unresolved_request:?}")
17677                    }
17678                }
17679            }
17680        })
17681        .detach();
17682
17683    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17684        let unresolved_item_1 = unresolved_item_1.clone();
17685        let unresolved_item_2 = unresolved_item_2.clone();
17686        async move {
17687            Ok(Some(lsp::CompletionResponse::Array(vec![
17688                unresolved_item_1,
17689                unresolved_item_2,
17690            ])))
17691        }
17692    })
17693    .next()
17694    .await;
17695
17696    cx.condition(|editor, _| editor.context_menu_visible())
17697        .await;
17698    cx.update_editor(|editor, _, _| {
17699        let context_menu = editor.context_menu.borrow_mut();
17700        let context_menu = context_menu
17701            .as_ref()
17702            .expect("Should have the context menu deployed");
17703        match context_menu {
17704            CodeContextMenu::Completions(completions_menu) => {
17705                let completions = completions_menu.completions.borrow_mut();
17706                assert_eq!(
17707                    completions
17708                        .iter()
17709                        .map(|completion| &completion.label.text)
17710                        .collect::<Vec<_>>(),
17711                    vec!["id", "other"]
17712                )
17713            }
17714            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17715        }
17716    });
17717    cx.run_until_parked();
17718
17719    cx.update_editor(|editor, window, cx| {
17720        editor.context_menu_next(&ContextMenuNext, window, cx);
17721    });
17722    cx.run_until_parked();
17723    cx.update_editor(|editor, window, cx| {
17724        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17725    });
17726    cx.run_until_parked();
17727    cx.update_editor(|editor, window, cx| {
17728        editor.context_menu_next(&ContextMenuNext, window, cx);
17729    });
17730    cx.run_until_parked();
17731    cx.update_editor(|editor, window, cx| {
17732        editor
17733            .compose_completion(&ComposeCompletion::default(), window, cx)
17734            .expect("No task returned")
17735    })
17736    .await
17737    .expect("Completion failed");
17738    cx.run_until_parked();
17739
17740    cx.update_editor(|editor, _, cx| {
17741        assert_eq!(
17742            resolve_requests_1.load(atomic::Ordering::Acquire),
17743            1,
17744            "Should always resolve once despite multiple selections"
17745        );
17746        assert_eq!(
17747            resolve_requests_2.load(atomic::Ordering::Acquire),
17748            1,
17749            "Should always resolve once after multiple selections and applying the completion"
17750        );
17751        assert_eq!(
17752            editor.text(cx),
17753            "fn main() { let a = ??.other; }",
17754            "Should use resolved data when applying the completion"
17755        );
17756    });
17757}
17758
17759#[gpui::test]
17760async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
17761    init_test(cx, |_| {});
17762
17763    let item_0 = lsp::CompletionItem {
17764        label: "abs".into(),
17765        insert_text: Some("abs".into()),
17766        data: Some(json!({ "very": "special"})),
17767        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
17768        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
17769            lsp::InsertReplaceEdit {
17770                new_text: "abs".to_string(),
17771                insert: lsp::Range::default(),
17772                replace: lsp::Range::default(),
17773            },
17774        )),
17775        ..lsp::CompletionItem::default()
17776    };
17777    let items = iter::once(item_0.clone())
17778        .chain((11..51).map(|i| lsp::CompletionItem {
17779            label: format!("item_{}", i),
17780            insert_text: Some(format!("item_{}", i)),
17781            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
17782            ..lsp::CompletionItem::default()
17783        }))
17784        .collect::<Vec<_>>();
17785
17786    let default_commit_characters = vec!["?".to_string()];
17787    let default_data = json!({ "default": "data"});
17788    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
17789    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
17790    let default_edit_range = lsp::Range {
17791        start: lsp::Position {
17792            line: 0,
17793            character: 5,
17794        },
17795        end: lsp::Position {
17796            line: 0,
17797            character: 5,
17798        },
17799    };
17800
17801    let mut cx = EditorLspTestContext::new_rust(
17802        lsp::ServerCapabilities {
17803            completion_provider: Some(lsp::CompletionOptions {
17804                trigger_characters: Some(vec![".".to_string()]),
17805                resolve_provider: Some(true),
17806                ..Default::default()
17807            }),
17808            ..Default::default()
17809        },
17810        cx,
17811    )
17812    .await;
17813
17814    cx.set_state("fn main() { let a = 2ˇ; }");
17815    cx.simulate_keystroke(".");
17816
17817    let completion_data = default_data.clone();
17818    let completion_characters = default_commit_characters.clone();
17819    let completion_items = items.clone();
17820    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17821        let default_data = completion_data.clone();
17822        let default_commit_characters = completion_characters.clone();
17823        let items = completion_items.clone();
17824        async move {
17825            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
17826                items,
17827                item_defaults: Some(lsp::CompletionListItemDefaults {
17828                    data: Some(default_data.clone()),
17829                    commit_characters: Some(default_commit_characters.clone()),
17830                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
17831                        default_edit_range,
17832                    )),
17833                    insert_text_format: Some(default_insert_text_format),
17834                    insert_text_mode: Some(default_insert_text_mode),
17835                }),
17836                ..lsp::CompletionList::default()
17837            })))
17838        }
17839    })
17840    .next()
17841    .await;
17842
17843    let resolved_items = Arc::new(Mutex::new(Vec::new()));
17844    cx.lsp
17845        .server
17846        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17847            let closure_resolved_items = resolved_items.clone();
17848            move |item_to_resolve, _| {
17849                let closure_resolved_items = closure_resolved_items.clone();
17850                async move {
17851                    closure_resolved_items.lock().push(item_to_resolve.clone());
17852                    Ok(item_to_resolve)
17853                }
17854            }
17855        })
17856        .detach();
17857
17858    cx.condition(|editor, _| editor.context_menu_visible())
17859        .await;
17860    cx.run_until_parked();
17861    cx.update_editor(|editor, _, _| {
17862        let menu = editor.context_menu.borrow_mut();
17863        match menu.as_ref().expect("should have the completions menu") {
17864            CodeContextMenu::Completions(completions_menu) => {
17865                assert_eq!(
17866                    completions_menu
17867                        .entries
17868                        .borrow()
17869                        .iter()
17870                        .map(|mat| mat.string.clone())
17871                        .collect::<Vec<String>>(),
17872                    items
17873                        .iter()
17874                        .map(|completion| completion.label.clone())
17875                        .collect::<Vec<String>>()
17876                );
17877            }
17878            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
17879        }
17880    });
17881    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
17882    // with 4 from the end.
17883    assert_eq!(
17884        *resolved_items.lock(),
17885        [&items[0..16], &items[items.len() - 4..items.len()]]
17886            .concat()
17887            .iter()
17888            .cloned()
17889            .map(|mut item| {
17890                if item.data.is_none() {
17891                    item.data = Some(default_data.clone());
17892                }
17893                item
17894            })
17895            .collect::<Vec<lsp::CompletionItem>>(),
17896        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
17897    );
17898    resolved_items.lock().clear();
17899
17900    cx.update_editor(|editor, window, cx| {
17901        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17902    });
17903    cx.run_until_parked();
17904    // Completions that have already been resolved are skipped.
17905    assert_eq!(
17906        *resolved_items.lock(),
17907        items[items.len() - 17..items.len() - 4]
17908            .iter()
17909            .cloned()
17910            .map(|mut item| {
17911                if item.data.is_none() {
17912                    item.data = Some(default_data.clone());
17913                }
17914                item
17915            })
17916            .collect::<Vec<lsp::CompletionItem>>()
17917    );
17918    resolved_items.lock().clear();
17919}
17920
17921#[gpui::test]
17922async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
17923    init_test(cx, |_| {});
17924
17925    let mut cx = EditorLspTestContext::new(
17926        Language::new(
17927            LanguageConfig {
17928                matcher: LanguageMatcher {
17929                    path_suffixes: vec!["jsx".into()],
17930                    ..Default::default()
17931                },
17932                overrides: [(
17933                    "element".into(),
17934                    LanguageConfigOverride {
17935                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
17936                        ..Default::default()
17937                    },
17938                )]
17939                .into_iter()
17940                .collect(),
17941                ..Default::default()
17942            },
17943            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
17944        )
17945        .with_override_query("(jsx_self_closing_element) @element")
17946        .unwrap(),
17947        lsp::ServerCapabilities {
17948            completion_provider: Some(lsp::CompletionOptions {
17949                trigger_characters: Some(vec![":".to_string()]),
17950                ..Default::default()
17951            }),
17952            ..Default::default()
17953        },
17954        cx,
17955    )
17956    .await;
17957
17958    cx.lsp
17959        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
17960            Ok(Some(lsp::CompletionResponse::Array(vec![
17961                lsp::CompletionItem {
17962                    label: "bg-blue".into(),
17963                    ..Default::default()
17964                },
17965                lsp::CompletionItem {
17966                    label: "bg-red".into(),
17967                    ..Default::default()
17968                },
17969                lsp::CompletionItem {
17970                    label: "bg-yellow".into(),
17971                    ..Default::default()
17972                },
17973            ])))
17974        });
17975
17976    cx.set_state(r#"<p class="bgˇ" />"#);
17977
17978    // Trigger completion when typing a dash, because the dash is an extra
17979    // word character in the 'element' scope, which contains the cursor.
17980    cx.simulate_keystroke("-");
17981    cx.executor().run_until_parked();
17982    cx.update_editor(|editor, _, _| {
17983        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17984        {
17985            assert_eq!(
17986                completion_menu_entries(menu),
17987                &["bg-blue", "bg-red", "bg-yellow"]
17988            );
17989        } else {
17990            panic!("expected completion menu to be open");
17991        }
17992    });
17993
17994    cx.simulate_keystroke("l");
17995    cx.executor().run_until_parked();
17996    cx.update_editor(|editor, _, _| {
17997        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17998        {
17999            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
18000        } else {
18001            panic!("expected completion menu to be open");
18002        }
18003    });
18004
18005    // When filtering completions, consider the character after the '-' to
18006    // be the start of a subword.
18007    cx.set_state(r#"<p class="yelˇ" />"#);
18008    cx.simulate_keystroke("l");
18009    cx.executor().run_until_parked();
18010    cx.update_editor(|editor, _, _| {
18011        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18012        {
18013            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
18014        } else {
18015            panic!("expected completion menu to be open");
18016        }
18017    });
18018}
18019
18020fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
18021    let entries = menu.entries.borrow();
18022    entries.iter().map(|mat| mat.string.clone()).collect()
18023}
18024
18025#[gpui::test]
18026async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
18027    init_test(cx, |settings| {
18028        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
18029            Formatter::Prettier,
18030        )))
18031    });
18032
18033    let fs = FakeFs::new(cx.executor());
18034    fs.insert_file(path!("/file.ts"), Default::default()).await;
18035
18036    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
18037    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18038
18039    language_registry.add(Arc::new(Language::new(
18040        LanguageConfig {
18041            name: "TypeScript".into(),
18042            matcher: LanguageMatcher {
18043                path_suffixes: vec!["ts".to_string()],
18044                ..Default::default()
18045            },
18046            ..Default::default()
18047        },
18048        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18049    )));
18050    update_test_language_settings(cx, |settings| {
18051        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
18052    });
18053
18054    let test_plugin = "test_plugin";
18055    let _ = language_registry.register_fake_lsp(
18056        "TypeScript",
18057        FakeLspAdapter {
18058            prettier_plugins: vec![test_plugin],
18059            ..Default::default()
18060        },
18061    );
18062
18063    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
18064    let buffer = project
18065        .update(cx, |project, cx| {
18066            project.open_local_buffer(path!("/file.ts"), cx)
18067        })
18068        .await
18069        .unwrap();
18070
18071    let buffer_text = "one\ntwo\nthree\n";
18072    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18073    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18074    editor.update_in(cx, |editor, window, cx| {
18075        editor.set_text(buffer_text, window, cx)
18076    });
18077
18078    editor
18079        .update_in(cx, |editor, window, cx| {
18080            editor.perform_format(
18081                project.clone(),
18082                FormatTrigger::Manual,
18083                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18084                window,
18085                cx,
18086            )
18087        })
18088        .unwrap()
18089        .await;
18090    assert_eq!(
18091        editor.update(cx, |editor, cx| editor.text(cx)),
18092        buffer_text.to_string() + prettier_format_suffix,
18093        "Test prettier formatting was not applied to the original buffer text",
18094    );
18095
18096    update_test_language_settings(cx, |settings| {
18097        settings.defaults.formatter = Some(SelectedFormatter::Auto)
18098    });
18099    let format = editor.update_in(cx, |editor, window, cx| {
18100        editor.perform_format(
18101            project.clone(),
18102            FormatTrigger::Manual,
18103            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18104            window,
18105            cx,
18106        )
18107    });
18108    format.await.unwrap();
18109    assert_eq!(
18110        editor.update(cx, |editor, cx| editor.text(cx)),
18111        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18112        "Autoformatting (via test prettier) was not applied to the original buffer text",
18113    );
18114}
18115
18116#[gpui::test]
18117async fn test_addition_reverts(cx: &mut TestAppContext) {
18118    init_test(cx, |_| {});
18119    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18120    let base_text = indoc! {r#"
18121        struct Row;
18122        struct Row1;
18123        struct Row2;
18124
18125        struct Row4;
18126        struct Row5;
18127        struct Row6;
18128
18129        struct Row8;
18130        struct Row9;
18131        struct Row10;"#};
18132
18133    // When addition hunks are not adjacent to carets, no hunk revert is performed
18134    assert_hunk_revert(
18135        indoc! {r#"struct Row;
18136                   struct Row1;
18137                   struct Row1.1;
18138                   struct Row1.2;
18139                   struct Row2;ˇ
18140
18141                   struct Row4;
18142                   struct Row5;
18143                   struct Row6;
18144
18145                   struct Row8;
18146                   ˇstruct Row9;
18147                   struct Row9.1;
18148                   struct Row9.2;
18149                   struct Row9.3;
18150                   struct Row10;"#},
18151        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18152        indoc! {r#"struct Row;
18153                   struct Row1;
18154                   struct Row1.1;
18155                   struct Row1.2;
18156                   struct Row2;ˇ
18157
18158                   struct Row4;
18159                   struct Row5;
18160                   struct Row6;
18161
18162                   struct Row8;
18163                   ˇstruct Row9;
18164                   struct Row9.1;
18165                   struct Row9.2;
18166                   struct Row9.3;
18167                   struct Row10;"#},
18168        base_text,
18169        &mut cx,
18170    );
18171    // Same for selections
18172    assert_hunk_revert(
18173        indoc! {r#"struct Row;
18174                   struct Row1;
18175                   struct Row2;
18176                   struct Row2.1;
18177                   struct Row2.2;
18178                   «ˇ
18179                   struct Row4;
18180                   struct» Row5;
18181                   «struct Row6;
18182                   ˇ»
18183                   struct Row9.1;
18184                   struct Row9.2;
18185                   struct Row9.3;
18186                   struct Row8;
18187                   struct Row9;
18188                   struct Row10;"#},
18189        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18190        indoc! {r#"struct Row;
18191                   struct Row1;
18192                   struct Row2;
18193                   struct Row2.1;
18194                   struct Row2.2;
18195                   «ˇ
18196                   struct Row4;
18197                   struct» Row5;
18198                   «struct Row6;
18199                   ˇ»
18200                   struct Row9.1;
18201                   struct Row9.2;
18202                   struct Row9.3;
18203                   struct Row8;
18204                   struct Row9;
18205                   struct Row10;"#},
18206        base_text,
18207        &mut cx,
18208    );
18209
18210    // When carets and selections intersect the addition hunks, those are reverted.
18211    // Adjacent carets got merged.
18212    assert_hunk_revert(
18213        indoc! {r#"struct Row;
18214                   ˇ// something on the top
18215                   struct Row1;
18216                   struct Row2;
18217                   struct Roˇw3.1;
18218                   struct Row2.2;
18219                   struct Row2.3;ˇ
18220
18221                   struct Row4;
18222                   struct ˇRow5.1;
18223                   struct Row5.2;
18224                   struct «Rowˇ»5.3;
18225                   struct Row5;
18226                   struct Row6;
18227                   ˇ
18228                   struct Row9.1;
18229                   struct «Rowˇ»9.2;
18230                   struct «ˇRow»9.3;
18231                   struct Row8;
18232                   struct Row9;
18233                   «ˇ// something on bottom»
18234                   struct Row10;"#},
18235        vec![
18236            DiffHunkStatusKind::Added,
18237            DiffHunkStatusKind::Added,
18238            DiffHunkStatusKind::Added,
18239            DiffHunkStatusKind::Added,
18240            DiffHunkStatusKind::Added,
18241        ],
18242        indoc! {r#"struct Row;
18243                   ˇstruct Row1;
18244                   struct Row2;
18245                   ˇ
18246                   struct Row4;
18247                   ˇstruct Row5;
18248                   struct Row6;
18249                   ˇ
18250                   ˇstruct Row8;
18251                   struct Row9;
18252                   ˇstruct Row10;"#},
18253        base_text,
18254        &mut cx,
18255    );
18256}
18257
18258#[gpui::test]
18259async fn test_modification_reverts(cx: &mut TestAppContext) {
18260    init_test(cx, |_| {});
18261    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18262    let base_text = indoc! {r#"
18263        struct Row;
18264        struct Row1;
18265        struct Row2;
18266
18267        struct Row4;
18268        struct Row5;
18269        struct Row6;
18270
18271        struct Row8;
18272        struct Row9;
18273        struct Row10;"#};
18274
18275    // Modification hunks behave the same as the addition ones.
18276    assert_hunk_revert(
18277        indoc! {r#"struct Row;
18278                   struct Row1;
18279                   struct Row33;
18280                   ˇ
18281                   struct Row4;
18282                   struct Row5;
18283                   struct Row6;
18284                   ˇ
18285                   struct Row99;
18286                   struct Row9;
18287                   struct Row10;"#},
18288        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18289        indoc! {r#"struct Row;
18290                   struct Row1;
18291                   struct Row33;
18292                   ˇ
18293                   struct Row4;
18294                   struct Row5;
18295                   struct Row6;
18296                   ˇ
18297                   struct Row99;
18298                   struct Row9;
18299                   struct Row10;"#},
18300        base_text,
18301        &mut cx,
18302    );
18303    assert_hunk_revert(
18304        indoc! {r#"struct Row;
18305                   struct Row1;
18306                   struct Row33;
18307                   «ˇ
18308                   struct Row4;
18309                   struct» Row5;
18310                   «struct Row6;
18311                   ˇ»
18312                   struct Row99;
18313                   struct Row9;
18314                   struct Row10;"#},
18315        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18316        indoc! {r#"struct Row;
18317                   struct Row1;
18318                   struct Row33;
18319                   «ˇ
18320                   struct Row4;
18321                   struct» Row5;
18322                   «struct Row6;
18323                   ˇ»
18324                   struct Row99;
18325                   struct Row9;
18326                   struct Row10;"#},
18327        base_text,
18328        &mut cx,
18329    );
18330
18331    assert_hunk_revert(
18332        indoc! {r#"ˇstruct Row1.1;
18333                   struct Row1;
18334                   «ˇstr»uct Row22;
18335
18336                   struct ˇRow44;
18337                   struct Row5;
18338                   struct «Rˇ»ow66;ˇ
18339
18340                   «struˇ»ct Row88;
18341                   struct Row9;
18342                   struct Row1011;ˇ"#},
18343        vec![
18344            DiffHunkStatusKind::Modified,
18345            DiffHunkStatusKind::Modified,
18346            DiffHunkStatusKind::Modified,
18347            DiffHunkStatusKind::Modified,
18348            DiffHunkStatusKind::Modified,
18349            DiffHunkStatusKind::Modified,
18350        ],
18351        indoc! {r#"struct Row;
18352                   ˇstruct Row1;
18353                   struct Row2;
18354                   ˇ
18355                   struct Row4;
18356                   ˇstruct Row5;
18357                   struct Row6;
18358                   ˇ
18359                   struct Row8;
18360                   ˇstruct Row9;
18361                   struct Row10;ˇ"#},
18362        base_text,
18363        &mut cx,
18364    );
18365}
18366
18367#[gpui::test]
18368async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18369    init_test(cx, |_| {});
18370    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18371    let base_text = indoc! {r#"
18372        one
18373
18374        two
18375        three
18376        "#};
18377
18378    cx.set_head_text(base_text);
18379    cx.set_state("\nˇ\n");
18380    cx.executor().run_until_parked();
18381    cx.update_editor(|editor, _window, cx| {
18382        editor.expand_selected_diff_hunks(cx);
18383    });
18384    cx.executor().run_until_parked();
18385    cx.update_editor(|editor, window, cx| {
18386        editor.backspace(&Default::default(), window, cx);
18387    });
18388    cx.run_until_parked();
18389    cx.assert_state_with_diff(
18390        indoc! {r#"
18391
18392        - two
18393        - threeˇ
18394        +
18395        "#}
18396        .to_string(),
18397    );
18398}
18399
18400#[gpui::test]
18401async fn test_deletion_reverts(cx: &mut TestAppContext) {
18402    init_test(cx, |_| {});
18403    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18404    let base_text = indoc! {r#"struct Row;
18405struct Row1;
18406struct Row2;
18407
18408struct Row4;
18409struct Row5;
18410struct Row6;
18411
18412struct Row8;
18413struct Row9;
18414struct Row10;"#};
18415
18416    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18417    assert_hunk_revert(
18418        indoc! {r#"struct Row;
18419                   struct Row2;
18420
18421                   ˇstruct Row4;
18422                   struct Row5;
18423                   struct Row6;
18424                   ˇ
18425                   struct Row8;
18426                   struct Row10;"#},
18427        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18428        indoc! {r#"struct Row;
18429                   struct Row2;
18430
18431                   ˇstruct Row4;
18432                   struct Row5;
18433                   struct Row6;
18434                   ˇ
18435                   struct Row8;
18436                   struct Row10;"#},
18437        base_text,
18438        &mut cx,
18439    );
18440    assert_hunk_revert(
18441        indoc! {r#"struct Row;
18442                   struct Row2;
18443
18444                   «ˇstruct Row4;
18445                   struct» Row5;
18446                   «struct Row6;
18447                   ˇ»
18448                   struct Row8;
18449                   struct Row10;"#},
18450        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18451        indoc! {r#"struct Row;
18452                   struct Row2;
18453
18454                   «ˇstruct Row4;
18455                   struct» Row5;
18456                   «struct Row6;
18457                   ˇ»
18458                   struct Row8;
18459                   struct Row10;"#},
18460        base_text,
18461        &mut cx,
18462    );
18463
18464    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18465    assert_hunk_revert(
18466        indoc! {r#"struct Row;
18467                   ˇstruct Row2;
18468
18469                   struct Row4;
18470                   struct Row5;
18471                   struct Row6;
18472
18473                   struct Row8;ˇ
18474                   struct Row10;"#},
18475        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18476        indoc! {r#"struct Row;
18477                   struct Row1;
18478                   ˇstruct Row2;
18479
18480                   struct Row4;
18481                   struct Row5;
18482                   struct Row6;
18483
18484                   struct Row8;ˇ
18485                   struct Row9;
18486                   struct Row10;"#},
18487        base_text,
18488        &mut cx,
18489    );
18490    assert_hunk_revert(
18491        indoc! {r#"struct Row;
18492                   struct Row2«ˇ;
18493                   struct Row4;
18494                   struct» Row5;
18495                   «struct Row6;
18496
18497                   struct Row8;ˇ»
18498                   struct Row10;"#},
18499        vec![
18500            DiffHunkStatusKind::Deleted,
18501            DiffHunkStatusKind::Deleted,
18502            DiffHunkStatusKind::Deleted,
18503        ],
18504        indoc! {r#"struct Row;
18505                   struct Row1;
18506                   struct Row2«ˇ;
18507
18508                   struct Row4;
18509                   struct» Row5;
18510                   «struct Row6;
18511
18512                   struct Row8;ˇ»
18513                   struct Row9;
18514                   struct Row10;"#},
18515        base_text,
18516        &mut cx,
18517    );
18518}
18519
18520#[gpui::test]
18521async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18522    init_test(cx, |_| {});
18523
18524    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18525    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18526    let base_text_3 =
18527        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18528
18529    let text_1 = edit_first_char_of_every_line(base_text_1);
18530    let text_2 = edit_first_char_of_every_line(base_text_2);
18531    let text_3 = edit_first_char_of_every_line(base_text_3);
18532
18533    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18534    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18535    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18536
18537    let multibuffer = cx.new(|cx| {
18538        let mut multibuffer = MultiBuffer::new(ReadWrite);
18539        multibuffer.push_excerpts(
18540            buffer_1.clone(),
18541            [
18542                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18543                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18544                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18545            ],
18546            cx,
18547        );
18548        multibuffer.push_excerpts(
18549            buffer_2.clone(),
18550            [
18551                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18552                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18553                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18554            ],
18555            cx,
18556        );
18557        multibuffer.push_excerpts(
18558            buffer_3.clone(),
18559            [
18560                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18561                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18562                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18563            ],
18564            cx,
18565        );
18566        multibuffer
18567    });
18568
18569    let fs = FakeFs::new(cx.executor());
18570    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
18571    let (editor, cx) = cx
18572        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
18573    editor.update_in(cx, |editor, _window, cx| {
18574        for (buffer, diff_base) in [
18575            (buffer_1.clone(), base_text_1),
18576            (buffer_2.clone(), base_text_2),
18577            (buffer_3.clone(), base_text_3),
18578        ] {
18579            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18580            editor
18581                .buffer
18582                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18583        }
18584    });
18585    cx.executor().run_until_parked();
18586
18587    editor.update_in(cx, |editor, window, cx| {
18588        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}");
18589        editor.select_all(&SelectAll, window, cx);
18590        editor.git_restore(&Default::default(), window, cx);
18591    });
18592    cx.executor().run_until_parked();
18593
18594    // When all ranges are selected, all buffer hunks are reverted.
18595    editor.update(cx, |editor, cx| {
18596        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");
18597    });
18598    buffer_1.update(cx, |buffer, _| {
18599        assert_eq!(buffer.text(), base_text_1);
18600    });
18601    buffer_2.update(cx, |buffer, _| {
18602        assert_eq!(buffer.text(), base_text_2);
18603    });
18604    buffer_3.update(cx, |buffer, _| {
18605        assert_eq!(buffer.text(), base_text_3);
18606    });
18607
18608    editor.update_in(cx, |editor, window, cx| {
18609        editor.undo(&Default::default(), window, cx);
18610    });
18611
18612    editor.update_in(cx, |editor, window, cx| {
18613        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18614            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
18615        });
18616        editor.git_restore(&Default::default(), window, cx);
18617    });
18618
18619    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
18620    // but not affect buffer_2 and its related excerpts.
18621    editor.update(cx, |editor, cx| {
18622        assert_eq!(
18623            editor.text(cx),
18624            "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}"
18625        );
18626    });
18627    buffer_1.update(cx, |buffer, _| {
18628        assert_eq!(buffer.text(), base_text_1);
18629    });
18630    buffer_2.update(cx, |buffer, _| {
18631        assert_eq!(
18632            buffer.text(),
18633            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
18634        );
18635    });
18636    buffer_3.update(cx, |buffer, _| {
18637        assert_eq!(
18638            buffer.text(),
18639            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
18640        );
18641    });
18642
18643    fn edit_first_char_of_every_line(text: &str) -> String {
18644        text.split('\n')
18645            .map(|line| format!("X{}", &line[1..]))
18646            .collect::<Vec<_>>()
18647            .join("\n")
18648    }
18649}
18650
18651#[gpui::test]
18652async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
18653    init_test(cx, |_| {});
18654
18655    let cols = 4;
18656    let rows = 10;
18657    let sample_text_1 = sample_text(rows, cols, 'a');
18658    assert_eq!(
18659        sample_text_1,
18660        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
18661    );
18662    let sample_text_2 = sample_text(rows, cols, 'l');
18663    assert_eq!(
18664        sample_text_2,
18665        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
18666    );
18667    let sample_text_3 = sample_text(rows, cols, 'v');
18668    assert_eq!(
18669        sample_text_3,
18670        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
18671    );
18672
18673    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
18674    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
18675    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
18676
18677    let multi_buffer = cx.new(|cx| {
18678        let mut multibuffer = MultiBuffer::new(ReadWrite);
18679        multibuffer.push_excerpts(
18680            buffer_1.clone(),
18681            [
18682                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18683                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18684                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18685            ],
18686            cx,
18687        );
18688        multibuffer.push_excerpts(
18689            buffer_2.clone(),
18690            [
18691                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18692                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18693                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18694            ],
18695            cx,
18696        );
18697        multibuffer.push_excerpts(
18698            buffer_3.clone(),
18699            [
18700                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18701                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18702                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18703            ],
18704            cx,
18705        );
18706        multibuffer
18707    });
18708
18709    let fs = FakeFs::new(cx.executor());
18710    fs.insert_tree(
18711        "/a",
18712        json!({
18713            "main.rs": sample_text_1,
18714            "other.rs": sample_text_2,
18715            "lib.rs": sample_text_3,
18716        }),
18717    )
18718    .await;
18719    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18720    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18721    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18722    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18723        Editor::new(
18724            EditorMode::full(),
18725            multi_buffer,
18726            Some(project.clone()),
18727            window,
18728            cx,
18729        )
18730    });
18731    let multibuffer_item_id = workspace
18732        .update(cx, |workspace, window, cx| {
18733            assert!(
18734                workspace.active_item(cx).is_none(),
18735                "active item should be None before the first item is added"
18736            );
18737            workspace.add_item_to_active_pane(
18738                Box::new(multi_buffer_editor.clone()),
18739                None,
18740                true,
18741                window,
18742                cx,
18743            );
18744            let active_item = workspace
18745                .active_item(cx)
18746                .expect("should have an active item after adding the multi buffer");
18747            assert!(
18748                !active_item.is_singleton(cx),
18749                "A multi buffer was expected to active after adding"
18750            );
18751            active_item.item_id()
18752        })
18753        .unwrap();
18754    cx.executor().run_until_parked();
18755
18756    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18757        editor.change_selections(
18758            SelectionEffects::scroll(Autoscroll::Next),
18759            window,
18760            cx,
18761            |s| s.select_ranges(Some(1..2)),
18762        );
18763        editor.open_excerpts(&OpenExcerpts, window, cx);
18764    });
18765    cx.executor().run_until_parked();
18766    let first_item_id = workspace
18767        .update(cx, |workspace, window, cx| {
18768            let active_item = workspace
18769                .active_item(cx)
18770                .expect("should have an active item after navigating into the 1st buffer");
18771            let first_item_id = active_item.item_id();
18772            assert_ne!(
18773                first_item_id, multibuffer_item_id,
18774                "Should navigate into the 1st buffer and activate it"
18775            );
18776            assert!(
18777                active_item.is_singleton(cx),
18778                "New active item should be a singleton buffer"
18779            );
18780            assert_eq!(
18781                active_item
18782                    .act_as::<Editor>(cx)
18783                    .expect("should have navigated into an editor for the 1st buffer")
18784                    .read(cx)
18785                    .text(cx),
18786                sample_text_1
18787            );
18788
18789            workspace
18790                .go_back(workspace.active_pane().downgrade(), window, cx)
18791                .detach_and_log_err(cx);
18792
18793            first_item_id
18794        })
18795        .unwrap();
18796    cx.executor().run_until_parked();
18797    workspace
18798        .update(cx, |workspace, _, cx| {
18799            let active_item = workspace
18800                .active_item(cx)
18801                .expect("should have an active item after navigating back");
18802            assert_eq!(
18803                active_item.item_id(),
18804                multibuffer_item_id,
18805                "Should navigate back to the multi buffer"
18806            );
18807            assert!(!active_item.is_singleton(cx));
18808        })
18809        .unwrap();
18810
18811    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18812        editor.change_selections(
18813            SelectionEffects::scroll(Autoscroll::Next),
18814            window,
18815            cx,
18816            |s| s.select_ranges(Some(39..40)),
18817        );
18818        editor.open_excerpts(&OpenExcerpts, window, cx);
18819    });
18820    cx.executor().run_until_parked();
18821    let second_item_id = workspace
18822        .update(cx, |workspace, window, cx| {
18823            let active_item = workspace
18824                .active_item(cx)
18825                .expect("should have an active item after navigating into the 2nd buffer");
18826            let second_item_id = active_item.item_id();
18827            assert_ne!(
18828                second_item_id, multibuffer_item_id,
18829                "Should navigate away from the multibuffer"
18830            );
18831            assert_ne!(
18832                second_item_id, first_item_id,
18833                "Should navigate into the 2nd buffer and activate it"
18834            );
18835            assert!(
18836                active_item.is_singleton(cx),
18837                "New active item should be a singleton buffer"
18838            );
18839            assert_eq!(
18840                active_item
18841                    .act_as::<Editor>(cx)
18842                    .expect("should have navigated into an editor")
18843                    .read(cx)
18844                    .text(cx),
18845                sample_text_2
18846            );
18847
18848            workspace
18849                .go_back(workspace.active_pane().downgrade(), window, cx)
18850                .detach_and_log_err(cx);
18851
18852            second_item_id
18853        })
18854        .unwrap();
18855    cx.executor().run_until_parked();
18856    workspace
18857        .update(cx, |workspace, _, cx| {
18858            let active_item = workspace
18859                .active_item(cx)
18860                .expect("should have an active item after navigating back from the 2nd buffer");
18861            assert_eq!(
18862                active_item.item_id(),
18863                multibuffer_item_id,
18864                "Should navigate back from the 2nd buffer to the multi buffer"
18865            );
18866            assert!(!active_item.is_singleton(cx));
18867        })
18868        .unwrap();
18869
18870    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18871        editor.change_selections(
18872            SelectionEffects::scroll(Autoscroll::Next),
18873            window,
18874            cx,
18875            |s| s.select_ranges(Some(70..70)),
18876        );
18877        editor.open_excerpts(&OpenExcerpts, window, cx);
18878    });
18879    cx.executor().run_until_parked();
18880    workspace
18881        .update(cx, |workspace, window, cx| {
18882            let active_item = workspace
18883                .active_item(cx)
18884                .expect("should have an active item after navigating into the 3rd buffer");
18885            let third_item_id = active_item.item_id();
18886            assert_ne!(
18887                third_item_id, multibuffer_item_id,
18888                "Should navigate into the 3rd buffer and activate it"
18889            );
18890            assert_ne!(third_item_id, first_item_id);
18891            assert_ne!(third_item_id, second_item_id);
18892            assert!(
18893                active_item.is_singleton(cx),
18894                "New active item should be a singleton buffer"
18895            );
18896            assert_eq!(
18897                active_item
18898                    .act_as::<Editor>(cx)
18899                    .expect("should have navigated into an editor")
18900                    .read(cx)
18901                    .text(cx),
18902                sample_text_3
18903            );
18904
18905            workspace
18906                .go_back(workspace.active_pane().downgrade(), window, cx)
18907                .detach_and_log_err(cx);
18908        })
18909        .unwrap();
18910    cx.executor().run_until_parked();
18911    workspace
18912        .update(cx, |workspace, _, cx| {
18913            let active_item = workspace
18914                .active_item(cx)
18915                .expect("should have an active item after navigating back from the 3rd buffer");
18916            assert_eq!(
18917                active_item.item_id(),
18918                multibuffer_item_id,
18919                "Should navigate back from the 3rd buffer to the multi buffer"
18920            );
18921            assert!(!active_item.is_singleton(cx));
18922        })
18923        .unwrap();
18924}
18925
18926#[gpui::test]
18927async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18928    init_test(cx, |_| {});
18929
18930    let mut cx = EditorTestContext::new(cx).await;
18931
18932    let diff_base = r#"
18933        use some::mod;
18934
18935        const A: u32 = 42;
18936
18937        fn main() {
18938            println!("hello");
18939
18940            println!("world");
18941        }
18942        "#
18943    .unindent();
18944
18945    cx.set_state(
18946        &r#"
18947        use some::modified;
18948
18949        ˇ
18950        fn main() {
18951            println!("hello there");
18952
18953            println!("around the");
18954            println!("world");
18955        }
18956        "#
18957        .unindent(),
18958    );
18959
18960    cx.set_head_text(&diff_base);
18961    executor.run_until_parked();
18962
18963    cx.update_editor(|editor, window, cx| {
18964        editor.go_to_next_hunk(&GoToHunk, window, cx);
18965        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18966    });
18967    executor.run_until_parked();
18968    cx.assert_state_with_diff(
18969        r#"
18970          use some::modified;
18971
18972
18973          fn main() {
18974        -     println!("hello");
18975        + ˇ    println!("hello there");
18976
18977              println!("around the");
18978              println!("world");
18979          }
18980        "#
18981        .unindent(),
18982    );
18983
18984    cx.update_editor(|editor, window, cx| {
18985        for _ in 0..2 {
18986            editor.go_to_next_hunk(&GoToHunk, window, cx);
18987            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18988        }
18989    });
18990    executor.run_until_parked();
18991    cx.assert_state_with_diff(
18992        r#"
18993        - use some::mod;
18994        + ˇuse some::modified;
18995
18996
18997          fn main() {
18998        -     println!("hello");
18999        +     println!("hello there");
19000
19001        +     println!("around the");
19002              println!("world");
19003          }
19004        "#
19005        .unindent(),
19006    );
19007
19008    cx.update_editor(|editor, window, cx| {
19009        editor.go_to_next_hunk(&GoToHunk, window, cx);
19010        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19011    });
19012    executor.run_until_parked();
19013    cx.assert_state_with_diff(
19014        r#"
19015        - use some::mod;
19016        + use some::modified;
19017
19018        - const A: u32 = 42;
19019          ˇ
19020          fn main() {
19021        -     println!("hello");
19022        +     println!("hello there");
19023
19024        +     println!("around the");
19025              println!("world");
19026          }
19027        "#
19028        .unindent(),
19029    );
19030
19031    cx.update_editor(|editor, window, cx| {
19032        editor.cancel(&Cancel, window, cx);
19033    });
19034
19035    cx.assert_state_with_diff(
19036        r#"
19037          use some::modified;
19038
19039          ˇ
19040          fn main() {
19041              println!("hello there");
19042
19043              println!("around the");
19044              println!("world");
19045          }
19046        "#
19047        .unindent(),
19048    );
19049}
19050
19051#[gpui::test]
19052async fn test_diff_base_change_with_expanded_diff_hunks(
19053    executor: BackgroundExecutor,
19054    cx: &mut TestAppContext,
19055) {
19056    init_test(cx, |_| {});
19057
19058    let mut cx = EditorTestContext::new(cx).await;
19059
19060    let diff_base = r#"
19061        use some::mod1;
19062        use some::mod2;
19063
19064        const A: u32 = 42;
19065        const B: u32 = 42;
19066        const C: u32 = 42;
19067
19068        fn main() {
19069            println!("hello");
19070
19071            println!("world");
19072        }
19073        "#
19074    .unindent();
19075
19076    cx.set_state(
19077        &r#"
19078        use some::mod2;
19079
19080        const A: u32 = 42;
19081        const C: u32 = 42;
19082
19083        fn main(ˇ) {
19084            //println!("hello");
19085
19086            println!("world");
19087            //
19088            //
19089        }
19090        "#
19091        .unindent(),
19092    );
19093
19094    cx.set_head_text(&diff_base);
19095    executor.run_until_parked();
19096
19097    cx.update_editor(|editor, window, cx| {
19098        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19099    });
19100    executor.run_until_parked();
19101    cx.assert_state_with_diff(
19102        r#"
19103        - use some::mod1;
19104          use some::mod2;
19105
19106          const A: u32 = 42;
19107        - const B: u32 = 42;
19108          const C: u32 = 42;
19109
19110          fn main(ˇ) {
19111        -     println!("hello");
19112        +     //println!("hello");
19113
19114              println!("world");
19115        +     //
19116        +     //
19117          }
19118        "#
19119        .unindent(),
19120    );
19121
19122    cx.set_head_text("new diff base!");
19123    executor.run_until_parked();
19124    cx.assert_state_with_diff(
19125        r#"
19126        - new diff base!
19127        + use some::mod2;
19128        +
19129        + const A: u32 = 42;
19130        + const C: u32 = 42;
19131        +
19132        + fn main(ˇ) {
19133        +     //println!("hello");
19134        +
19135        +     println!("world");
19136        +     //
19137        +     //
19138        + }
19139        "#
19140        .unindent(),
19141    );
19142}
19143
19144#[gpui::test]
19145async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19146    init_test(cx, |_| {});
19147
19148    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19149    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19150    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19151    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19152    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19153    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19154
19155    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19156    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19157    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19158
19159    let multi_buffer = cx.new(|cx| {
19160        let mut multibuffer = MultiBuffer::new(ReadWrite);
19161        multibuffer.push_excerpts(
19162            buffer_1.clone(),
19163            [
19164                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19165                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19166                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19167            ],
19168            cx,
19169        );
19170        multibuffer.push_excerpts(
19171            buffer_2.clone(),
19172            [
19173                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19174                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19175                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19176            ],
19177            cx,
19178        );
19179        multibuffer.push_excerpts(
19180            buffer_3.clone(),
19181            [
19182                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19183                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19184                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19185            ],
19186            cx,
19187        );
19188        multibuffer
19189    });
19190
19191    let editor =
19192        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19193    editor
19194        .update(cx, |editor, _window, cx| {
19195            for (buffer, diff_base) in [
19196                (buffer_1.clone(), file_1_old),
19197                (buffer_2.clone(), file_2_old),
19198                (buffer_3.clone(), file_3_old),
19199            ] {
19200                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19201                editor
19202                    .buffer
19203                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19204            }
19205        })
19206        .unwrap();
19207
19208    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19209    cx.run_until_parked();
19210
19211    cx.assert_editor_state(
19212        &"
19213            ˇaaa
19214            ccc
19215            ddd
19216
19217            ggg
19218            hhh
19219
19220
19221            lll
19222            mmm
19223            NNN
19224
19225            qqq
19226            rrr
19227
19228            uuu
19229            111
19230            222
19231            333
19232
19233            666
19234            777
19235
19236            000
19237            !!!"
19238        .unindent(),
19239    );
19240
19241    cx.update_editor(|editor, window, cx| {
19242        editor.select_all(&SelectAll, window, cx);
19243        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19244    });
19245    cx.executor().run_until_parked();
19246
19247    cx.assert_state_with_diff(
19248        "
19249            «aaa
19250          - bbb
19251            ccc
19252            ddd
19253
19254            ggg
19255            hhh
19256
19257
19258            lll
19259            mmm
19260          - nnn
19261          + NNN
19262
19263            qqq
19264            rrr
19265
19266            uuu
19267            111
19268            222
19269            333
19270
19271          + 666
19272            777
19273
19274            000
19275            !!!ˇ»"
19276            .unindent(),
19277    );
19278}
19279
19280#[gpui::test]
19281async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19282    init_test(cx, |_| {});
19283
19284    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19285    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19286
19287    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19288    let multi_buffer = cx.new(|cx| {
19289        let mut multibuffer = MultiBuffer::new(ReadWrite);
19290        multibuffer.push_excerpts(
19291            buffer.clone(),
19292            [
19293                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19294                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19295                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19296            ],
19297            cx,
19298        );
19299        multibuffer
19300    });
19301
19302    let editor =
19303        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19304    editor
19305        .update(cx, |editor, _window, cx| {
19306            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19307            editor
19308                .buffer
19309                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19310        })
19311        .unwrap();
19312
19313    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19314    cx.run_until_parked();
19315
19316    cx.update_editor(|editor, window, cx| {
19317        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19318    });
19319    cx.executor().run_until_parked();
19320
19321    // When the start of a hunk coincides with the start of its excerpt,
19322    // the hunk is expanded. When the start of a hunk is earlier than
19323    // the start of its excerpt, the hunk is not expanded.
19324    cx.assert_state_with_diff(
19325        "
19326            ˇaaa
19327          - bbb
19328          + BBB
19329
19330          - ddd
19331          - eee
19332          + DDD
19333          + EEE
19334            fff
19335
19336            iii
19337        "
19338        .unindent(),
19339    );
19340}
19341
19342#[gpui::test]
19343async fn test_edits_around_expanded_insertion_hunks(
19344    executor: BackgroundExecutor,
19345    cx: &mut TestAppContext,
19346) {
19347    init_test(cx, |_| {});
19348
19349    let mut cx = EditorTestContext::new(cx).await;
19350
19351    let diff_base = r#"
19352        use some::mod1;
19353        use some::mod2;
19354
19355        const A: u32 = 42;
19356
19357        fn main() {
19358            println!("hello");
19359
19360            println!("world");
19361        }
19362        "#
19363    .unindent();
19364    executor.run_until_parked();
19365    cx.set_state(
19366        &r#"
19367        use some::mod1;
19368        use some::mod2;
19369
19370        const A: u32 = 42;
19371        const B: u32 = 42;
19372        const C: u32 = 42;
19373        ˇ
19374
19375        fn main() {
19376            println!("hello");
19377
19378            println!("world");
19379        }
19380        "#
19381        .unindent(),
19382    );
19383
19384    cx.set_head_text(&diff_base);
19385    executor.run_until_parked();
19386
19387    cx.update_editor(|editor, window, cx| {
19388        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19389    });
19390    executor.run_until_parked();
19391
19392    cx.assert_state_with_diff(
19393        r#"
19394        use some::mod1;
19395        use some::mod2;
19396
19397        const A: u32 = 42;
19398      + const B: u32 = 42;
19399      + const C: u32 = 42;
19400      + ˇ
19401
19402        fn main() {
19403            println!("hello");
19404
19405            println!("world");
19406        }
19407      "#
19408        .unindent(),
19409    );
19410
19411    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19412    executor.run_until_parked();
19413
19414    cx.assert_state_with_diff(
19415        r#"
19416        use some::mod1;
19417        use some::mod2;
19418
19419        const A: u32 = 42;
19420      + const B: u32 = 42;
19421      + const C: u32 = 42;
19422      + const D: u32 = 42;
19423      + ˇ
19424
19425        fn main() {
19426            println!("hello");
19427
19428            println!("world");
19429        }
19430      "#
19431        .unindent(),
19432    );
19433
19434    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19435    executor.run_until_parked();
19436
19437    cx.assert_state_with_diff(
19438        r#"
19439        use some::mod1;
19440        use some::mod2;
19441
19442        const A: u32 = 42;
19443      + const B: u32 = 42;
19444      + const C: u32 = 42;
19445      + const D: u32 = 42;
19446      + const E: u32 = 42;
19447      + ˇ
19448
19449        fn main() {
19450            println!("hello");
19451
19452            println!("world");
19453        }
19454      "#
19455        .unindent(),
19456    );
19457
19458    cx.update_editor(|editor, window, cx| {
19459        editor.delete_line(&DeleteLine, window, cx);
19460    });
19461    executor.run_until_parked();
19462
19463    cx.assert_state_with_diff(
19464        r#"
19465        use some::mod1;
19466        use some::mod2;
19467
19468        const A: u32 = 42;
19469      + const B: u32 = 42;
19470      + const C: u32 = 42;
19471      + const D: u32 = 42;
19472      + const E: u32 = 42;
19473        ˇ
19474        fn main() {
19475            println!("hello");
19476
19477            println!("world");
19478        }
19479      "#
19480        .unindent(),
19481    );
19482
19483    cx.update_editor(|editor, window, cx| {
19484        editor.move_up(&MoveUp, window, cx);
19485        editor.delete_line(&DeleteLine, window, cx);
19486        editor.move_up(&MoveUp, window, cx);
19487        editor.delete_line(&DeleteLine, window, cx);
19488        editor.move_up(&MoveUp, window, cx);
19489        editor.delete_line(&DeleteLine, window, cx);
19490    });
19491    executor.run_until_parked();
19492    cx.assert_state_with_diff(
19493        r#"
19494        use some::mod1;
19495        use some::mod2;
19496
19497        const A: u32 = 42;
19498      + const B: u32 = 42;
19499        ˇ
19500        fn main() {
19501            println!("hello");
19502
19503            println!("world");
19504        }
19505      "#
19506        .unindent(),
19507    );
19508
19509    cx.update_editor(|editor, window, cx| {
19510        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19511        editor.delete_line(&DeleteLine, window, cx);
19512    });
19513    executor.run_until_parked();
19514    cx.assert_state_with_diff(
19515        r#"
19516        ˇ
19517        fn main() {
19518            println!("hello");
19519
19520            println!("world");
19521        }
19522      "#
19523        .unindent(),
19524    );
19525}
19526
19527#[gpui::test]
19528async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19529    init_test(cx, |_| {});
19530
19531    let mut cx = EditorTestContext::new(cx).await;
19532    cx.set_head_text(indoc! { "
19533        one
19534        two
19535        three
19536        four
19537        five
19538        "
19539    });
19540    cx.set_state(indoc! { "
19541        one
19542        ˇthree
19543        five
19544    "});
19545    cx.run_until_parked();
19546    cx.update_editor(|editor, window, cx| {
19547        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19548    });
19549    cx.assert_state_with_diff(
19550        indoc! { "
19551        one
19552      - two
19553        ˇthree
19554      - four
19555        five
19556    "}
19557        .to_string(),
19558    );
19559    cx.update_editor(|editor, window, cx| {
19560        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19561    });
19562
19563    cx.assert_state_with_diff(
19564        indoc! { "
19565        one
19566        ˇthree
19567        five
19568    "}
19569        .to_string(),
19570    );
19571
19572    cx.set_state(indoc! { "
19573        one
19574        ˇTWO
19575        three
19576        four
19577        five
19578    "});
19579    cx.run_until_parked();
19580    cx.update_editor(|editor, window, cx| {
19581        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19582    });
19583
19584    cx.assert_state_with_diff(
19585        indoc! { "
19586            one
19587          - two
19588          + ˇTWO
19589            three
19590            four
19591            five
19592        "}
19593        .to_string(),
19594    );
19595    cx.update_editor(|editor, window, cx| {
19596        editor.move_up(&Default::default(), window, cx);
19597        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19598    });
19599    cx.assert_state_with_diff(
19600        indoc! { "
19601            one
19602            ˇTWO
19603            three
19604            four
19605            five
19606        "}
19607        .to_string(),
19608    );
19609}
19610
19611#[gpui::test]
19612async fn test_edits_around_expanded_deletion_hunks(
19613    executor: BackgroundExecutor,
19614    cx: &mut TestAppContext,
19615) {
19616    init_test(cx, |_| {});
19617
19618    let mut cx = EditorTestContext::new(cx).await;
19619
19620    let diff_base = r#"
19621        use some::mod1;
19622        use some::mod2;
19623
19624        const A: u32 = 42;
19625        const B: u32 = 42;
19626        const C: u32 = 42;
19627
19628
19629        fn main() {
19630            println!("hello");
19631
19632            println!("world");
19633        }
19634    "#
19635    .unindent();
19636    executor.run_until_parked();
19637    cx.set_state(
19638        &r#"
19639        use some::mod1;
19640        use some::mod2;
19641
19642        ˇconst B: u32 = 42;
19643        const C: u32 = 42;
19644
19645
19646        fn main() {
19647            println!("hello");
19648
19649            println!("world");
19650        }
19651        "#
19652        .unindent(),
19653    );
19654
19655    cx.set_head_text(&diff_base);
19656    executor.run_until_parked();
19657
19658    cx.update_editor(|editor, window, cx| {
19659        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19660    });
19661    executor.run_until_parked();
19662
19663    cx.assert_state_with_diff(
19664        r#"
19665        use some::mod1;
19666        use some::mod2;
19667
19668      - const A: u32 = 42;
19669        ˇconst B: u32 = 42;
19670        const C: u32 = 42;
19671
19672
19673        fn main() {
19674            println!("hello");
19675
19676            println!("world");
19677        }
19678      "#
19679        .unindent(),
19680    );
19681
19682    cx.update_editor(|editor, window, cx| {
19683        editor.delete_line(&DeleteLine, window, cx);
19684    });
19685    executor.run_until_parked();
19686    cx.assert_state_with_diff(
19687        r#"
19688        use some::mod1;
19689        use some::mod2;
19690
19691      - const A: u32 = 42;
19692      - const B: u32 = 42;
19693        ˇconst C: u32 = 42;
19694
19695
19696        fn main() {
19697            println!("hello");
19698
19699            println!("world");
19700        }
19701      "#
19702        .unindent(),
19703    );
19704
19705    cx.update_editor(|editor, window, cx| {
19706        editor.delete_line(&DeleteLine, window, cx);
19707    });
19708    executor.run_until_parked();
19709    cx.assert_state_with_diff(
19710        r#"
19711        use some::mod1;
19712        use some::mod2;
19713
19714      - const A: u32 = 42;
19715      - const B: u32 = 42;
19716      - const C: u32 = 42;
19717        ˇ
19718
19719        fn main() {
19720            println!("hello");
19721
19722            println!("world");
19723        }
19724      "#
19725        .unindent(),
19726    );
19727
19728    cx.update_editor(|editor, window, cx| {
19729        editor.handle_input("replacement", window, cx);
19730    });
19731    executor.run_until_parked();
19732    cx.assert_state_with_diff(
19733        r#"
19734        use some::mod1;
19735        use some::mod2;
19736
19737      - const A: u32 = 42;
19738      - const B: u32 = 42;
19739      - const C: u32 = 42;
19740      -
19741      + replacementˇ
19742
19743        fn main() {
19744            println!("hello");
19745
19746            println!("world");
19747        }
19748      "#
19749        .unindent(),
19750    );
19751}
19752
19753#[gpui::test]
19754async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19755    init_test(cx, |_| {});
19756
19757    let mut cx = EditorTestContext::new(cx).await;
19758
19759    let base_text = r#"
19760        one
19761        two
19762        three
19763        four
19764        five
19765    "#
19766    .unindent();
19767    executor.run_until_parked();
19768    cx.set_state(
19769        &r#"
19770        one
19771        two
19772        fˇour
19773        five
19774        "#
19775        .unindent(),
19776    );
19777
19778    cx.set_head_text(&base_text);
19779    executor.run_until_parked();
19780
19781    cx.update_editor(|editor, window, cx| {
19782        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19783    });
19784    executor.run_until_parked();
19785
19786    cx.assert_state_with_diff(
19787        r#"
19788          one
19789          two
19790        - three
19791          fˇour
19792          five
19793        "#
19794        .unindent(),
19795    );
19796
19797    cx.update_editor(|editor, window, cx| {
19798        editor.backspace(&Backspace, window, cx);
19799        editor.backspace(&Backspace, window, cx);
19800    });
19801    executor.run_until_parked();
19802    cx.assert_state_with_diff(
19803        r#"
19804          one
19805          two
19806        - threeˇ
19807        - four
19808        + our
19809          five
19810        "#
19811        .unindent(),
19812    );
19813}
19814
19815#[gpui::test]
19816async fn test_edit_after_expanded_modification_hunk(
19817    executor: BackgroundExecutor,
19818    cx: &mut TestAppContext,
19819) {
19820    init_test(cx, |_| {});
19821
19822    let mut cx = EditorTestContext::new(cx).await;
19823
19824    let diff_base = r#"
19825        use some::mod1;
19826        use some::mod2;
19827
19828        const A: u32 = 42;
19829        const B: u32 = 42;
19830        const C: u32 = 42;
19831        const D: u32 = 42;
19832
19833
19834        fn main() {
19835            println!("hello");
19836
19837            println!("world");
19838        }"#
19839    .unindent();
19840
19841    cx.set_state(
19842        &r#"
19843        use some::mod1;
19844        use some::mod2;
19845
19846        const A: u32 = 42;
19847        const B: u32 = 42;
19848        const C: u32 = 43ˇ
19849        const D: u32 = 42;
19850
19851
19852        fn main() {
19853            println!("hello");
19854
19855            println!("world");
19856        }"#
19857        .unindent(),
19858    );
19859
19860    cx.set_head_text(&diff_base);
19861    executor.run_until_parked();
19862    cx.update_editor(|editor, window, cx| {
19863        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19864    });
19865    executor.run_until_parked();
19866
19867    cx.assert_state_with_diff(
19868        r#"
19869        use some::mod1;
19870        use some::mod2;
19871
19872        const A: u32 = 42;
19873        const B: u32 = 42;
19874      - const C: u32 = 42;
19875      + const C: u32 = 43ˇ
19876        const D: u32 = 42;
19877
19878
19879        fn main() {
19880            println!("hello");
19881
19882            println!("world");
19883        }"#
19884        .unindent(),
19885    );
19886
19887    cx.update_editor(|editor, window, cx| {
19888        editor.handle_input("\nnew_line\n", window, cx);
19889    });
19890    executor.run_until_parked();
19891
19892    cx.assert_state_with_diff(
19893        r#"
19894        use some::mod1;
19895        use some::mod2;
19896
19897        const A: u32 = 42;
19898        const B: u32 = 42;
19899      - const C: u32 = 42;
19900      + const C: u32 = 43
19901      + new_line
19902      + ˇ
19903        const D: u32 = 42;
19904
19905
19906        fn main() {
19907            println!("hello");
19908
19909            println!("world");
19910        }"#
19911        .unindent(),
19912    );
19913}
19914
19915#[gpui::test]
19916async fn test_stage_and_unstage_added_file_hunk(
19917    executor: BackgroundExecutor,
19918    cx: &mut TestAppContext,
19919) {
19920    init_test(cx, |_| {});
19921
19922    let mut cx = EditorTestContext::new(cx).await;
19923    cx.update_editor(|editor, _, cx| {
19924        editor.set_expand_all_diff_hunks(cx);
19925    });
19926
19927    let working_copy = r#"
19928            ˇfn main() {
19929                println!("hello, world!");
19930            }
19931        "#
19932    .unindent();
19933
19934    cx.set_state(&working_copy);
19935    executor.run_until_parked();
19936
19937    cx.assert_state_with_diff(
19938        r#"
19939            + ˇfn main() {
19940            +     println!("hello, world!");
19941            + }
19942        "#
19943        .unindent(),
19944    );
19945    cx.assert_index_text(None);
19946
19947    cx.update_editor(|editor, window, cx| {
19948        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19949    });
19950    executor.run_until_parked();
19951    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
19952    cx.assert_state_with_diff(
19953        r#"
19954            + ˇfn main() {
19955            +     println!("hello, world!");
19956            + }
19957        "#
19958        .unindent(),
19959    );
19960
19961    cx.update_editor(|editor, window, cx| {
19962        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19963    });
19964    executor.run_until_parked();
19965    cx.assert_index_text(None);
19966}
19967
19968async fn setup_indent_guides_editor(
19969    text: &str,
19970    cx: &mut TestAppContext,
19971) -> (BufferId, EditorTestContext) {
19972    init_test(cx, |_| {});
19973
19974    let mut cx = EditorTestContext::new(cx).await;
19975
19976    let buffer_id = cx.update_editor(|editor, window, cx| {
19977        editor.set_text(text, window, cx);
19978        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
19979
19980        buffer_ids[0]
19981    });
19982
19983    (buffer_id, cx)
19984}
19985
19986fn assert_indent_guides(
19987    range: Range<u32>,
19988    expected: Vec<IndentGuide>,
19989    active_indices: Option<Vec<usize>>,
19990    cx: &mut EditorTestContext,
19991) {
19992    let indent_guides = cx.update_editor(|editor, window, cx| {
19993        let snapshot = editor.snapshot(window, cx).display_snapshot;
19994        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
19995            editor,
19996            MultiBufferRow(range.start)..MultiBufferRow(range.end),
19997            true,
19998            &snapshot,
19999            cx,
20000        );
20001
20002        indent_guides.sort_by(|a, b| {
20003            a.depth.cmp(&b.depth).then(
20004                a.start_row
20005                    .cmp(&b.start_row)
20006                    .then(a.end_row.cmp(&b.end_row)),
20007            )
20008        });
20009        indent_guides
20010    });
20011
20012    if let Some(expected) = active_indices {
20013        let active_indices = cx.update_editor(|editor, window, cx| {
20014            let snapshot = editor.snapshot(window, cx).display_snapshot;
20015            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
20016        });
20017
20018        assert_eq!(
20019            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
20020            expected,
20021            "Active indent guide indices do not match"
20022        );
20023    }
20024
20025    assert_eq!(indent_guides, expected, "Indent guides do not match");
20026}
20027
20028fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
20029    IndentGuide {
20030        buffer_id,
20031        start_row: MultiBufferRow(start_row),
20032        end_row: MultiBufferRow(end_row),
20033        depth,
20034        tab_size: 4,
20035        settings: IndentGuideSettings {
20036            enabled: true,
20037            line_width: 1,
20038            active_line_width: 1,
20039            coloring: IndentGuideColoring::default(),
20040            background_coloring: IndentGuideBackgroundColoring::default(),
20041        },
20042    }
20043}
20044
20045#[gpui::test]
20046async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
20047    let (buffer_id, mut cx) = setup_indent_guides_editor(
20048        &"
20049        fn main() {
20050            let a = 1;
20051        }"
20052        .unindent(),
20053        cx,
20054    )
20055    .await;
20056
20057    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20058}
20059
20060#[gpui::test]
20061async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
20062    let (buffer_id, mut cx) = setup_indent_guides_editor(
20063        &"
20064        fn main() {
20065            let a = 1;
20066            let b = 2;
20067        }"
20068        .unindent(),
20069        cx,
20070    )
20071    .await;
20072
20073    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20074}
20075
20076#[gpui::test]
20077async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20078    let (buffer_id, mut cx) = setup_indent_guides_editor(
20079        &"
20080        fn main() {
20081            let a = 1;
20082            if a == 3 {
20083                let b = 2;
20084            } else {
20085                let c = 3;
20086            }
20087        }"
20088        .unindent(),
20089        cx,
20090    )
20091    .await;
20092
20093    assert_indent_guides(
20094        0..8,
20095        vec![
20096            indent_guide(buffer_id, 1, 6, 0),
20097            indent_guide(buffer_id, 3, 3, 1),
20098            indent_guide(buffer_id, 5, 5, 1),
20099        ],
20100        None,
20101        &mut cx,
20102    );
20103}
20104
20105#[gpui::test]
20106async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20107    let (buffer_id, mut cx) = setup_indent_guides_editor(
20108        &"
20109        fn main() {
20110            let a = 1;
20111                let b = 2;
20112            let c = 3;
20113        }"
20114        .unindent(),
20115        cx,
20116    )
20117    .await;
20118
20119    assert_indent_guides(
20120        0..5,
20121        vec![
20122            indent_guide(buffer_id, 1, 3, 0),
20123            indent_guide(buffer_id, 2, 2, 1),
20124        ],
20125        None,
20126        &mut cx,
20127    );
20128}
20129
20130#[gpui::test]
20131async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20132    let (buffer_id, mut cx) = setup_indent_guides_editor(
20133        &"
20134        fn main() {
20135            let a = 1;
20136
20137            let c = 3;
20138        }"
20139        .unindent(),
20140        cx,
20141    )
20142    .await;
20143
20144    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20145}
20146
20147#[gpui::test]
20148async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20149    let (buffer_id, mut cx) = setup_indent_guides_editor(
20150        &"
20151        fn main() {
20152            let a = 1;
20153
20154            let c = 3;
20155
20156            if a == 3 {
20157                let b = 2;
20158            } else {
20159                let c = 3;
20160            }
20161        }"
20162        .unindent(),
20163        cx,
20164    )
20165    .await;
20166
20167    assert_indent_guides(
20168        0..11,
20169        vec![
20170            indent_guide(buffer_id, 1, 9, 0),
20171            indent_guide(buffer_id, 6, 6, 1),
20172            indent_guide(buffer_id, 8, 8, 1),
20173        ],
20174        None,
20175        &mut cx,
20176    );
20177}
20178
20179#[gpui::test]
20180async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20181    let (buffer_id, mut cx) = setup_indent_guides_editor(
20182        &"
20183        fn main() {
20184            let a = 1;
20185
20186            let c = 3;
20187
20188            if a == 3 {
20189                let b = 2;
20190            } else {
20191                let c = 3;
20192            }
20193        }"
20194        .unindent(),
20195        cx,
20196    )
20197    .await;
20198
20199    assert_indent_guides(
20200        1..11,
20201        vec![
20202            indent_guide(buffer_id, 1, 9, 0),
20203            indent_guide(buffer_id, 6, 6, 1),
20204            indent_guide(buffer_id, 8, 8, 1),
20205        ],
20206        None,
20207        &mut cx,
20208    );
20209}
20210
20211#[gpui::test]
20212async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20213    let (buffer_id, mut cx) = setup_indent_guides_editor(
20214        &"
20215        fn main() {
20216            let a = 1;
20217
20218            let c = 3;
20219
20220            if a == 3 {
20221                let b = 2;
20222            } else {
20223                let c = 3;
20224            }
20225        }"
20226        .unindent(),
20227        cx,
20228    )
20229    .await;
20230
20231    assert_indent_guides(
20232        1..10,
20233        vec![
20234            indent_guide(buffer_id, 1, 9, 0),
20235            indent_guide(buffer_id, 6, 6, 1),
20236            indent_guide(buffer_id, 8, 8, 1),
20237        ],
20238        None,
20239        &mut cx,
20240    );
20241}
20242
20243#[gpui::test]
20244async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20245    let (buffer_id, mut cx) = setup_indent_guides_editor(
20246        &"
20247        fn main() {
20248            if a {
20249                b(
20250                    c,
20251                    d,
20252                )
20253            } else {
20254                e(
20255                    f
20256                )
20257            }
20258        }"
20259        .unindent(),
20260        cx,
20261    )
20262    .await;
20263
20264    assert_indent_guides(
20265        0..11,
20266        vec![
20267            indent_guide(buffer_id, 1, 10, 0),
20268            indent_guide(buffer_id, 2, 5, 1),
20269            indent_guide(buffer_id, 7, 9, 1),
20270            indent_guide(buffer_id, 3, 4, 2),
20271            indent_guide(buffer_id, 8, 8, 2),
20272        ],
20273        None,
20274        &mut cx,
20275    );
20276
20277    cx.update_editor(|editor, window, cx| {
20278        editor.fold_at(MultiBufferRow(2), window, cx);
20279        assert_eq!(
20280            editor.display_text(cx),
20281            "
20282            fn main() {
20283                if a {
20284                    b(⋯
20285                    )
20286                } else {
20287                    e(
20288                        f
20289                    )
20290                }
20291            }"
20292            .unindent()
20293        );
20294    });
20295
20296    assert_indent_guides(
20297        0..11,
20298        vec![
20299            indent_guide(buffer_id, 1, 10, 0),
20300            indent_guide(buffer_id, 2, 5, 1),
20301            indent_guide(buffer_id, 7, 9, 1),
20302            indent_guide(buffer_id, 8, 8, 2),
20303        ],
20304        None,
20305        &mut cx,
20306    );
20307}
20308
20309#[gpui::test]
20310async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20311    let (buffer_id, mut cx) = setup_indent_guides_editor(
20312        &"
20313        block1
20314            block2
20315                block3
20316                    block4
20317            block2
20318        block1
20319        block1"
20320            .unindent(),
20321        cx,
20322    )
20323    .await;
20324
20325    assert_indent_guides(
20326        1..10,
20327        vec![
20328            indent_guide(buffer_id, 1, 4, 0),
20329            indent_guide(buffer_id, 2, 3, 1),
20330            indent_guide(buffer_id, 3, 3, 2),
20331        ],
20332        None,
20333        &mut cx,
20334    );
20335}
20336
20337#[gpui::test]
20338async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20339    let (buffer_id, mut cx) = setup_indent_guides_editor(
20340        &"
20341        block1
20342            block2
20343                block3
20344
20345        block1
20346        block1"
20347            .unindent(),
20348        cx,
20349    )
20350    .await;
20351
20352    assert_indent_guides(
20353        0..6,
20354        vec![
20355            indent_guide(buffer_id, 1, 2, 0),
20356            indent_guide(buffer_id, 2, 2, 1),
20357        ],
20358        None,
20359        &mut cx,
20360    );
20361}
20362
20363#[gpui::test]
20364async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20365    let (buffer_id, mut cx) = setup_indent_guides_editor(
20366        &"
20367        function component() {
20368        \treturn (
20369        \t\t\t
20370        \t\t<div>
20371        \t\t\t<abc></abc>
20372        \t\t</div>
20373        \t)
20374        }"
20375        .unindent(),
20376        cx,
20377    )
20378    .await;
20379
20380    assert_indent_guides(
20381        0..8,
20382        vec![
20383            indent_guide(buffer_id, 1, 6, 0),
20384            indent_guide(buffer_id, 2, 5, 1),
20385            indent_guide(buffer_id, 4, 4, 2),
20386        ],
20387        None,
20388        &mut cx,
20389    );
20390}
20391
20392#[gpui::test]
20393async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20394    let (buffer_id, mut cx) = setup_indent_guides_editor(
20395        &"
20396        function component() {
20397        \treturn (
20398        \t
20399        \t\t<div>
20400        \t\t\t<abc></abc>
20401        \t\t</div>
20402        \t)
20403        }"
20404        .unindent(),
20405        cx,
20406    )
20407    .await;
20408
20409    assert_indent_guides(
20410        0..8,
20411        vec![
20412            indent_guide(buffer_id, 1, 6, 0),
20413            indent_guide(buffer_id, 2, 5, 1),
20414            indent_guide(buffer_id, 4, 4, 2),
20415        ],
20416        None,
20417        &mut cx,
20418    );
20419}
20420
20421#[gpui::test]
20422async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20423    let (buffer_id, mut cx) = setup_indent_guides_editor(
20424        &"
20425        block1
20426
20427
20428
20429            block2
20430        "
20431        .unindent(),
20432        cx,
20433    )
20434    .await;
20435
20436    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20437}
20438
20439#[gpui::test]
20440async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20441    let (buffer_id, mut cx) = setup_indent_guides_editor(
20442        &"
20443        def a:
20444        \tb = 3
20445        \tif True:
20446        \t\tc = 4
20447        \t\td = 5
20448        \tprint(b)
20449        "
20450        .unindent(),
20451        cx,
20452    )
20453    .await;
20454
20455    assert_indent_guides(
20456        0..6,
20457        vec![
20458            indent_guide(buffer_id, 1, 5, 0),
20459            indent_guide(buffer_id, 3, 4, 1),
20460        ],
20461        None,
20462        &mut cx,
20463    );
20464}
20465
20466#[gpui::test]
20467async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20468    let (buffer_id, mut cx) = setup_indent_guides_editor(
20469        &"
20470    fn main() {
20471        let a = 1;
20472    }"
20473        .unindent(),
20474        cx,
20475    )
20476    .await;
20477
20478    cx.update_editor(|editor, window, cx| {
20479        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20480            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20481        });
20482    });
20483
20484    assert_indent_guides(
20485        0..3,
20486        vec![indent_guide(buffer_id, 1, 1, 0)],
20487        Some(vec![0]),
20488        &mut cx,
20489    );
20490}
20491
20492#[gpui::test]
20493async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20494    let (buffer_id, mut cx) = setup_indent_guides_editor(
20495        &"
20496    fn main() {
20497        if 1 == 2 {
20498            let a = 1;
20499        }
20500    }"
20501        .unindent(),
20502        cx,
20503    )
20504    .await;
20505
20506    cx.update_editor(|editor, window, cx| {
20507        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20508            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20509        });
20510    });
20511
20512    assert_indent_guides(
20513        0..4,
20514        vec![
20515            indent_guide(buffer_id, 1, 3, 0),
20516            indent_guide(buffer_id, 2, 2, 1),
20517        ],
20518        Some(vec![1]),
20519        &mut cx,
20520    );
20521
20522    cx.update_editor(|editor, window, cx| {
20523        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20524            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20525        });
20526    });
20527
20528    assert_indent_guides(
20529        0..4,
20530        vec![
20531            indent_guide(buffer_id, 1, 3, 0),
20532            indent_guide(buffer_id, 2, 2, 1),
20533        ],
20534        Some(vec![1]),
20535        &mut cx,
20536    );
20537
20538    cx.update_editor(|editor, window, cx| {
20539        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20540            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20541        });
20542    });
20543
20544    assert_indent_guides(
20545        0..4,
20546        vec![
20547            indent_guide(buffer_id, 1, 3, 0),
20548            indent_guide(buffer_id, 2, 2, 1),
20549        ],
20550        Some(vec![0]),
20551        &mut cx,
20552    );
20553}
20554
20555#[gpui::test]
20556async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
20557    let (buffer_id, mut cx) = setup_indent_guides_editor(
20558        &"
20559    fn main() {
20560        let a = 1;
20561
20562        let b = 2;
20563    }"
20564        .unindent(),
20565        cx,
20566    )
20567    .await;
20568
20569    cx.update_editor(|editor, window, cx| {
20570        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20571            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20572        });
20573    });
20574
20575    assert_indent_guides(
20576        0..5,
20577        vec![indent_guide(buffer_id, 1, 3, 0)],
20578        Some(vec![0]),
20579        &mut cx,
20580    );
20581}
20582
20583#[gpui::test]
20584async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
20585    let (buffer_id, mut cx) = setup_indent_guides_editor(
20586        &"
20587    def m:
20588        a = 1
20589        pass"
20590            .unindent(),
20591        cx,
20592    )
20593    .await;
20594
20595    cx.update_editor(|editor, window, cx| {
20596        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20597            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20598        });
20599    });
20600
20601    assert_indent_guides(
20602        0..3,
20603        vec![indent_guide(buffer_id, 1, 2, 0)],
20604        Some(vec![0]),
20605        &mut cx,
20606    );
20607}
20608
20609#[gpui::test]
20610async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
20611    init_test(cx, |_| {});
20612    let mut cx = EditorTestContext::new(cx).await;
20613    let text = indoc! {
20614        "
20615        impl A {
20616            fn b() {
20617                0;
20618                3;
20619                5;
20620                6;
20621                7;
20622            }
20623        }
20624        "
20625    };
20626    let base_text = indoc! {
20627        "
20628        impl A {
20629            fn b() {
20630                0;
20631                1;
20632                2;
20633                3;
20634                4;
20635            }
20636            fn c() {
20637                5;
20638                6;
20639                7;
20640            }
20641        }
20642        "
20643    };
20644
20645    cx.update_editor(|editor, window, cx| {
20646        editor.set_text(text, window, cx);
20647
20648        editor.buffer().update(cx, |multibuffer, cx| {
20649            let buffer = multibuffer.as_singleton().unwrap();
20650            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
20651
20652            multibuffer.set_all_diff_hunks_expanded(cx);
20653            multibuffer.add_diff(diff, cx);
20654
20655            buffer.read(cx).remote_id()
20656        })
20657    });
20658    cx.run_until_parked();
20659
20660    cx.assert_state_with_diff(
20661        indoc! { "
20662          impl A {
20663              fn b() {
20664                  0;
20665        -         1;
20666        -         2;
20667                  3;
20668        -         4;
20669        -     }
20670        -     fn c() {
20671                  5;
20672                  6;
20673                  7;
20674              }
20675          }
20676          ˇ"
20677        }
20678        .to_string(),
20679    );
20680
20681    let mut actual_guides = cx.update_editor(|editor, window, cx| {
20682        editor
20683            .snapshot(window, cx)
20684            .buffer_snapshot
20685            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
20686            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
20687            .collect::<Vec<_>>()
20688    });
20689    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
20690    assert_eq!(
20691        actual_guides,
20692        vec![
20693            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
20694            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
20695            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
20696        ]
20697    );
20698}
20699
20700#[gpui::test]
20701async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20702    init_test(cx, |_| {});
20703    let mut cx = EditorTestContext::new(cx).await;
20704
20705    let diff_base = r#"
20706        a
20707        b
20708        c
20709        "#
20710    .unindent();
20711
20712    cx.set_state(
20713        &r#"
20714        ˇA
20715        b
20716        C
20717        "#
20718        .unindent(),
20719    );
20720    cx.set_head_text(&diff_base);
20721    cx.update_editor(|editor, window, cx| {
20722        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20723    });
20724    executor.run_until_parked();
20725
20726    let both_hunks_expanded = r#"
20727        - a
20728        + ˇA
20729          b
20730        - c
20731        + C
20732        "#
20733    .unindent();
20734
20735    cx.assert_state_with_diff(both_hunks_expanded.clone());
20736
20737    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20738        let snapshot = editor.snapshot(window, cx);
20739        let hunks = editor
20740            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20741            .collect::<Vec<_>>();
20742        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20743        let buffer_id = hunks[0].buffer_id;
20744        hunks
20745            .into_iter()
20746            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20747            .collect::<Vec<_>>()
20748    });
20749    assert_eq!(hunk_ranges.len(), 2);
20750
20751    cx.update_editor(|editor, _, cx| {
20752        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20753    });
20754    executor.run_until_parked();
20755
20756    let second_hunk_expanded = r#"
20757          ˇA
20758          b
20759        - c
20760        + C
20761        "#
20762    .unindent();
20763
20764    cx.assert_state_with_diff(second_hunk_expanded);
20765
20766    cx.update_editor(|editor, _, cx| {
20767        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20768    });
20769    executor.run_until_parked();
20770
20771    cx.assert_state_with_diff(both_hunks_expanded.clone());
20772
20773    cx.update_editor(|editor, _, cx| {
20774        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20775    });
20776    executor.run_until_parked();
20777
20778    let first_hunk_expanded = r#"
20779        - a
20780        + ˇA
20781          b
20782          C
20783        "#
20784    .unindent();
20785
20786    cx.assert_state_with_diff(first_hunk_expanded);
20787
20788    cx.update_editor(|editor, _, cx| {
20789        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20790    });
20791    executor.run_until_parked();
20792
20793    cx.assert_state_with_diff(both_hunks_expanded);
20794
20795    cx.set_state(
20796        &r#"
20797        ˇA
20798        b
20799        "#
20800        .unindent(),
20801    );
20802    cx.run_until_parked();
20803
20804    // TODO this cursor position seems bad
20805    cx.assert_state_with_diff(
20806        r#"
20807        - ˇa
20808        + A
20809          b
20810        "#
20811        .unindent(),
20812    );
20813
20814    cx.update_editor(|editor, window, cx| {
20815        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20816    });
20817
20818    cx.assert_state_with_diff(
20819        r#"
20820            - ˇa
20821            + A
20822              b
20823            - c
20824            "#
20825        .unindent(),
20826    );
20827
20828    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20829        let snapshot = editor.snapshot(window, cx);
20830        let hunks = editor
20831            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20832            .collect::<Vec<_>>();
20833        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20834        let buffer_id = hunks[0].buffer_id;
20835        hunks
20836            .into_iter()
20837            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20838            .collect::<Vec<_>>()
20839    });
20840    assert_eq!(hunk_ranges.len(), 2);
20841
20842    cx.update_editor(|editor, _, cx| {
20843        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20844    });
20845    executor.run_until_parked();
20846
20847    cx.assert_state_with_diff(
20848        r#"
20849        - ˇa
20850        + A
20851          b
20852        "#
20853        .unindent(),
20854    );
20855}
20856
20857#[gpui::test]
20858async fn test_toggle_deletion_hunk_at_start_of_file(
20859    executor: BackgroundExecutor,
20860    cx: &mut TestAppContext,
20861) {
20862    init_test(cx, |_| {});
20863    let mut cx = EditorTestContext::new(cx).await;
20864
20865    let diff_base = r#"
20866        a
20867        b
20868        c
20869        "#
20870    .unindent();
20871
20872    cx.set_state(
20873        &r#"
20874        ˇb
20875        c
20876        "#
20877        .unindent(),
20878    );
20879    cx.set_head_text(&diff_base);
20880    cx.update_editor(|editor, window, cx| {
20881        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20882    });
20883    executor.run_until_parked();
20884
20885    let hunk_expanded = r#"
20886        - a
20887          ˇb
20888          c
20889        "#
20890    .unindent();
20891
20892    cx.assert_state_with_diff(hunk_expanded.clone());
20893
20894    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20895        let snapshot = editor.snapshot(window, cx);
20896        let hunks = editor
20897            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20898            .collect::<Vec<_>>();
20899        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20900        let buffer_id = hunks[0].buffer_id;
20901        hunks
20902            .into_iter()
20903            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20904            .collect::<Vec<_>>()
20905    });
20906    assert_eq!(hunk_ranges.len(), 1);
20907
20908    cx.update_editor(|editor, _, cx| {
20909        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20910    });
20911    executor.run_until_parked();
20912
20913    let hunk_collapsed = r#"
20914          ˇb
20915          c
20916        "#
20917    .unindent();
20918
20919    cx.assert_state_with_diff(hunk_collapsed);
20920
20921    cx.update_editor(|editor, _, cx| {
20922        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20923    });
20924    executor.run_until_parked();
20925
20926    cx.assert_state_with_diff(hunk_expanded);
20927}
20928
20929#[gpui::test]
20930async fn test_display_diff_hunks(cx: &mut TestAppContext) {
20931    init_test(cx, |_| {});
20932
20933    let fs = FakeFs::new(cx.executor());
20934    fs.insert_tree(
20935        path!("/test"),
20936        json!({
20937            ".git": {},
20938            "file-1": "ONE\n",
20939            "file-2": "TWO\n",
20940            "file-3": "THREE\n",
20941        }),
20942    )
20943    .await;
20944
20945    fs.set_head_for_repo(
20946        path!("/test/.git").as_ref(),
20947        &[
20948            ("file-1", "one\n".into()),
20949            ("file-2", "two\n".into()),
20950            ("file-3", "three\n".into()),
20951        ],
20952        "deadbeef",
20953    );
20954
20955    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
20956    let mut buffers = vec![];
20957    for i in 1..=3 {
20958        let buffer = project
20959            .update(cx, |project, cx| {
20960                let path = format!(path!("/test/file-{}"), i);
20961                project.open_local_buffer(path, cx)
20962            })
20963            .await
20964            .unwrap();
20965        buffers.push(buffer);
20966    }
20967
20968    let multibuffer = cx.new(|cx| {
20969        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
20970        multibuffer.set_all_diff_hunks_expanded(cx);
20971        for buffer in &buffers {
20972            let snapshot = buffer.read(cx).snapshot();
20973            multibuffer.set_excerpts_for_path(
20974                PathKey::namespaced(
20975                    0,
20976                    buffer.read(cx).file().unwrap().path().as_unix_str().into(),
20977                ),
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}