editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    edit_prediction_tests::FakeEditPredictionProvider,
    6    linked_editing_ranges::LinkedEditingRanges,
    7    scroll::scroll_amount::ScrollAmount,
    8    test::{
    9        assert_text_with_selections, build_editor,
   10        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   11        editor_test_context::EditorTestContext,
   12        select_ranges,
   13    },
   14};
   15use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   16use collections::HashMap;
   17use futures::{StreamExt, channel::oneshot};
   18use gpui::{
   19    BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
   20    VisualTestContext, WindowBounds, WindowOptions, div,
   21};
   22use indoc::indoc;
   23use language::{
   24    BracketPairConfig,
   25    Capability::ReadWrite,
   26    DiagnosticSourceKind, FakeLspAdapter, IndentGuideSettings, LanguageConfig,
   27    LanguageConfigOverride, LanguageMatcher, LanguageName, Override, Point,
   28    language_settings::{
   29        CompletionSettingsContent, FormatterList, LanguageSettingsContent, LspInsertMode,
   30        SelectedFormatter,
   31    },
   32    tree_sitter_python,
   33};
   34use language_settings::Formatter;
   35use lsp::CompletionParams;
   36use multi_buffer::{IndentGuide, PathKey};
   37use parking_lot::Mutex;
   38use pretty_assertions::{assert_eq, assert_ne};
   39use project::{
   40    FakeFs,
   41    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   42    project_settings::LspSettings,
   43};
   44use serde_json::{self, json};
   45use settings::{
   46    AllLanguageSettingsContent, IndentGuideBackgroundColoring, IndentGuideColoring,
   47    ProjectSettingsContent,
   48};
   49use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   50use std::{
   51    iter,
   52    sync::atomic::{self, AtomicUsize},
   53};
   54use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
   55use text::ToPoint as _;
   56use unindent::Unindent;
   57use util::{
   58    assert_set_eq, path,
   59    rel_path::rel_path,
   60    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   61    uri,
   62};
   63use workspace::{
   64    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   65    OpenOptions, ViewId,
   66    invalid_buffer_view::InvalidBufferView,
   67    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   68    register_project_item,
   69};
   70
   71#[gpui::test]
   72fn test_edit_events(cx: &mut TestAppContext) {
   73    init_test(cx, |_| {});
   74
   75    let buffer = cx.new(|cx| {
   76        let mut buffer = language::Buffer::local("123456", cx);
   77        buffer.set_group_interval(Duration::from_secs(1));
   78        buffer
   79    });
   80
   81    let events = Rc::new(RefCell::new(Vec::new()));
   82    let editor1 = cx.add_window({
   83        let events = events.clone();
   84        |window, cx| {
   85            let entity = cx.entity();
   86            cx.subscribe_in(
   87                &entity,
   88                window,
   89                move |_, _, event: &EditorEvent, _, _| match event {
   90                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   91                    EditorEvent::BufferEdited => {
   92                        events.borrow_mut().push(("editor1", "buffer edited"))
   93                    }
   94                    _ => {}
   95                },
   96            )
   97            .detach();
   98            Editor::for_buffer(buffer.clone(), None, window, cx)
   99        }
  100    });
  101
  102    let editor2 = cx.add_window({
  103        let events = events.clone();
  104        |window, cx| {
  105            cx.subscribe_in(
  106                &cx.entity(),
  107                window,
  108                move |_, _, event: &EditorEvent, _, _| match event {
  109                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  110                    EditorEvent::BufferEdited => {
  111                        events.borrow_mut().push(("editor2", "buffer edited"))
  112                    }
  113                    _ => {}
  114                },
  115            )
  116            .detach();
  117            Editor::for_buffer(buffer.clone(), None, window, cx)
  118        }
  119    });
  120
  121    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  122
  123    // Mutating editor 1 will emit an `Edited` event only for that editor.
  124    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  125    assert_eq!(
  126        mem::take(&mut *events.borrow_mut()),
  127        [
  128            ("editor1", "edited"),
  129            ("editor1", "buffer edited"),
  130            ("editor2", "buffer edited"),
  131        ]
  132    );
  133
  134    // Mutating editor 2 will emit an `Edited` event only for that editor.
  135    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  136    assert_eq!(
  137        mem::take(&mut *events.borrow_mut()),
  138        [
  139            ("editor2", "edited"),
  140            ("editor1", "buffer edited"),
  141            ("editor2", "buffer edited"),
  142        ]
  143    );
  144
  145    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  146    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  147    assert_eq!(
  148        mem::take(&mut *events.borrow_mut()),
  149        [
  150            ("editor1", "edited"),
  151            ("editor1", "buffer edited"),
  152            ("editor2", "buffer edited"),
  153        ]
  154    );
  155
  156    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  157    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  158    assert_eq!(
  159        mem::take(&mut *events.borrow_mut()),
  160        [
  161            ("editor1", "edited"),
  162            ("editor1", "buffer edited"),
  163            ("editor2", "buffer edited"),
  164        ]
  165    );
  166
  167    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  168    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  169    assert_eq!(
  170        mem::take(&mut *events.borrow_mut()),
  171        [
  172            ("editor2", "edited"),
  173            ("editor1", "buffer edited"),
  174            ("editor2", "buffer edited"),
  175        ]
  176    );
  177
  178    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  179    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  180    assert_eq!(
  181        mem::take(&mut *events.borrow_mut()),
  182        [
  183            ("editor2", "edited"),
  184            ("editor1", "buffer edited"),
  185            ("editor2", "buffer edited"),
  186        ]
  187    );
  188
  189    // No event is emitted when the mutation is a no-op.
  190    _ = editor2.update(cx, |editor, window, cx| {
  191        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  192            s.select_ranges([0..0])
  193        });
  194
  195        editor.backspace(&Backspace, window, cx);
  196    });
  197    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  198}
  199
  200#[gpui::test]
  201fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  202    init_test(cx, |_| {});
  203
  204    let mut now = Instant::now();
  205    let group_interval = Duration::from_millis(1);
  206    let buffer = cx.new(|cx| {
  207        let mut buf = language::Buffer::local("123456", cx);
  208        buf.set_group_interval(group_interval);
  209        buf
  210    });
  211    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  212    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  213
  214    _ = editor.update(cx, |editor, window, cx| {
  215        editor.start_transaction_at(now, window, cx);
  216        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  217            s.select_ranges([2..4])
  218        });
  219
  220        editor.insert("cd", window, cx);
  221        editor.end_transaction_at(now, cx);
  222        assert_eq!(editor.text(cx), "12cd56");
  223        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  224
  225        editor.start_transaction_at(now, window, cx);
  226        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  227            s.select_ranges([4..5])
  228        });
  229        editor.insert("e", window, cx);
  230        editor.end_transaction_at(now, cx);
  231        assert_eq!(editor.text(cx), "12cde6");
  232        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  233
  234        now += group_interval + Duration::from_millis(1);
  235        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  236            s.select_ranges([2..2])
  237        });
  238
  239        // Simulate an edit in another editor
  240        buffer.update(cx, |buffer, cx| {
  241            buffer.start_transaction_at(now, cx);
  242            buffer.edit([(0..1, "a")], None, cx);
  243            buffer.edit([(1..1, "b")], None, cx);
  244            buffer.end_transaction_at(now, cx);
  245        });
  246
  247        assert_eq!(editor.text(cx), "ab2cde6");
  248        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  249
  250        // Last transaction happened past the group interval in a different editor.
  251        // Undo it individually and don't restore selections.
  252        editor.undo(&Undo, window, cx);
  253        assert_eq!(editor.text(cx), "12cde6");
  254        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  255
  256        // First two transactions happened within the group interval in this editor.
  257        // Undo them together and restore selections.
  258        editor.undo(&Undo, window, cx);
  259        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  260        assert_eq!(editor.text(cx), "123456");
  261        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  262
  263        // Redo the first two transactions together.
  264        editor.redo(&Redo, window, cx);
  265        assert_eq!(editor.text(cx), "12cde6");
  266        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  267
  268        // Redo the last transaction on its own.
  269        editor.redo(&Redo, window, cx);
  270        assert_eq!(editor.text(cx), "ab2cde6");
  271        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  272
  273        // Test empty transactions.
  274        editor.start_transaction_at(now, window, cx);
  275        editor.end_transaction_at(now, cx);
  276        editor.undo(&Undo, window, cx);
  277        assert_eq!(editor.text(cx), "12cde6");
  278    });
  279}
  280
  281#[gpui::test]
  282fn test_ime_composition(cx: &mut TestAppContext) {
  283    init_test(cx, |_| {});
  284
  285    let buffer = cx.new(|cx| {
  286        let mut buffer = language::Buffer::local("abcde", cx);
  287        // Ensure automatic grouping doesn't occur.
  288        buffer.set_group_interval(Duration::ZERO);
  289        buffer
  290    });
  291
  292    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  293    cx.add_window(|window, cx| {
  294        let mut editor = build_editor(buffer.clone(), window, cx);
  295
  296        // Start a new IME composition.
  297        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  298        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  299        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  300        assert_eq!(editor.text(cx), "äbcde");
  301        assert_eq!(
  302            editor.marked_text_ranges(cx),
  303            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  304        );
  305
  306        // Finalize IME composition.
  307        editor.replace_text_in_range(None, "ā", window, cx);
  308        assert_eq!(editor.text(cx), "ābcde");
  309        assert_eq!(editor.marked_text_ranges(cx), None);
  310
  311        // IME composition edits are grouped and are undone/redone at once.
  312        editor.undo(&Default::default(), window, cx);
  313        assert_eq!(editor.text(cx), "abcde");
  314        assert_eq!(editor.marked_text_ranges(cx), None);
  315        editor.redo(&Default::default(), window, cx);
  316        assert_eq!(editor.text(cx), "ābcde");
  317        assert_eq!(editor.marked_text_ranges(cx), None);
  318
  319        // Start a new IME composition.
  320        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  321        assert_eq!(
  322            editor.marked_text_ranges(cx),
  323            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  324        );
  325
  326        // Undoing during an IME composition cancels it.
  327        editor.undo(&Default::default(), window, cx);
  328        assert_eq!(editor.text(cx), "ābcde");
  329        assert_eq!(editor.marked_text_ranges(cx), None);
  330
  331        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  332        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  333        assert_eq!(editor.text(cx), "ābcdè");
  334        assert_eq!(
  335            editor.marked_text_ranges(cx),
  336            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  337        );
  338
  339        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  340        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  341        assert_eq!(editor.text(cx), "ābcdę");
  342        assert_eq!(editor.marked_text_ranges(cx), None);
  343
  344        // Start a new IME composition with multiple cursors.
  345        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  346            s.select_ranges([
  347                OffsetUtf16(1)..OffsetUtf16(1),
  348                OffsetUtf16(3)..OffsetUtf16(3),
  349                OffsetUtf16(5)..OffsetUtf16(5),
  350            ])
  351        });
  352        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  353        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  354        assert_eq!(
  355            editor.marked_text_ranges(cx),
  356            Some(vec![
  357                OffsetUtf16(0)..OffsetUtf16(3),
  358                OffsetUtf16(4)..OffsetUtf16(7),
  359                OffsetUtf16(8)..OffsetUtf16(11)
  360            ])
  361        );
  362
  363        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  364        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  365        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  366        assert_eq!(
  367            editor.marked_text_ranges(cx),
  368            Some(vec![
  369                OffsetUtf16(1)..OffsetUtf16(2),
  370                OffsetUtf16(5)..OffsetUtf16(6),
  371                OffsetUtf16(9)..OffsetUtf16(10)
  372            ])
  373        );
  374
  375        // Finalize IME composition with multiple cursors.
  376        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  377        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  378        assert_eq!(editor.marked_text_ranges(cx), None);
  379
  380        editor
  381    });
  382}
  383
  384#[gpui::test]
  385fn test_selection_with_mouse(cx: &mut TestAppContext) {
  386    init_test(cx, |_| {});
  387
  388    let editor = cx.add_window(|window, cx| {
  389        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  390        build_editor(buffer, window, cx)
  391    });
  392
  393    _ = editor.update(cx, |editor, window, cx| {
  394        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  395    });
  396    assert_eq!(
  397        editor
  398            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  399            .unwrap(),
  400        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  401    );
  402
  403    _ = editor.update(cx, |editor, window, cx| {
  404        editor.update_selection(
  405            DisplayPoint::new(DisplayRow(3), 3),
  406            0,
  407            gpui::Point::<f32>::default(),
  408            window,
  409            cx,
  410        );
  411    });
  412
  413    assert_eq!(
  414        editor
  415            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  416            .unwrap(),
  417        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  418    );
  419
  420    _ = editor.update(cx, |editor, window, cx| {
  421        editor.update_selection(
  422            DisplayPoint::new(DisplayRow(1), 1),
  423            0,
  424            gpui::Point::<f32>::default(),
  425            window,
  426            cx,
  427        );
  428    });
  429
  430    assert_eq!(
  431        editor
  432            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  433            .unwrap(),
  434        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  435    );
  436
  437    _ = editor.update(cx, |editor, window, cx| {
  438        editor.end_selection(window, cx);
  439        editor.update_selection(
  440            DisplayPoint::new(DisplayRow(3), 3),
  441            0,
  442            gpui::Point::<f32>::default(),
  443            window,
  444            cx,
  445        );
  446    });
  447
  448    assert_eq!(
  449        editor
  450            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  451            .unwrap(),
  452        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  453    );
  454
  455    _ = editor.update(cx, |editor, window, cx| {
  456        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  457        editor.update_selection(
  458            DisplayPoint::new(DisplayRow(0), 0),
  459            0,
  460            gpui::Point::<f32>::default(),
  461            window,
  462            cx,
  463        );
  464    });
  465
  466    assert_eq!(
  467        editor
  468            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  469            .unwrap(),
  470        [
  471            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  472            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  473        ]
  474    );
  475
  476    _ = editor.update(cx, |editor, window, cx| {
  477        editor.end_selection(window, cx);
  478    });
  479
  480    assert_eq!(
  481        editor
  482            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  483            .unwrap(),
  484        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  485    );
  486}
  487
  488#[gpui::test]
  489fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  490    init_test(cx, |_| {});
  491
  492    let editor = cx.add_window(|window, cx| {
  493        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  494        build_editor(buffer, window, cx)
  495    });
  496
  497    _ = editor.update(cx, |editor, window, cx| {
  498        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  499    });
  500
  501    _ = editor.update(cx, |editor, window, cx| {
  502        editor.end_selection(window, cx);
  503    });
  504
  505    _ = editor.update(cx, |editor, window, cx| {
  506        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  507    });
  508
  509    _ = editor.update(cx, |editor, window, cx| {
  510        editor.end_selection(window, cx);
  511    });
  512
  513    assert_eq!(
  514        editor
  515            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  516            .unwrap(),
  517        [
  518            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  519            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  520        ]
  521    );
  522
  523    _ = editor.update(cx, |editor, window, cx| {
  524        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  525    });
  526
  527    _ = editor.update(cx, |editor, window, cx| {
  528        editor.end_selection(window, cx);
  529    });
  530
  531    assert_eq!(
  532        editor
  533            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  534            .unwrap(),
  535        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  536    );
  537}
  538
  539#[gpui::test]
  540fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  541    init_test(cx, |_| {});
  542
  543    let editor = cx.add_window(|window, cx| {
  544        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  545        build_editor(buffer, window, cx)
  546    });
  547
  548    _ = editor.update(cx, |editor, window, cx| {
  549        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  550        assert_eq!(
  551            editor.selections.display_ranges(cx),
  552            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  553        );
  554    });
  555
  556    _ = editor.update(cx, |editor, window, cx| {
  557        editor.update_selection(
  558            DisplayPoint::new(DisplayRow(3), 3),
  559            0,
  560            gpui::Point::<f32>::default(),
  561            window,
  562            cx,
  563        );
  564        assert_eq!(
  565            editor.selections.display_ranges(cx),
  566            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  567        );
  568    });
  569
  570    _ = editor.update(cx, |editor, window, cx| {
  571        editor.cancel(&Cancel, window, cx);
  572        editor.update_selection(
  573            DisplayPoint::new(DisplayRow(1), 1),
  574            0,
  575            gpui::Point::<f32>::default(),
  576            window,
  577            cx,
  578        );
  579        assert_eq!(
  580            editor.selections.display_ranges(cx),
  581            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  582        );
  583    });
  584}
  585
  586#[gpui::test]
  587fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  588    init_test(cx, |_| {});
  589
  590    let editor = cx.add_window(|window, cx| {
  591        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  592        build_editor(buffer, window, cx)
  593    });
  594
  595    _ = editor.update(cx, |editor, window, cx| {
  596        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  597        assert_eq!(
  598            editor.selections.display_ranges(cx),
  599            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  600        );
  601
  602        editor.move_down(&Default::default(), window, cx);
  603        assert_eq!(
  604            editor.selections.display_ranges(cx),
  605            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  606        );
  607
  608        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  609        assert_eq!(
  610            editor.selections.display_ranges(cx),
  611            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  612        );
  613
  614        editor.move_up(&Default::default(), window, cx);
  615        assert_eq!(
  616            editor.selections.display_ranges(cx),
  617            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  618        );
  619    });
  620}
  621
  622#[gpui::test]
  623fn test_clone(cx: &mut TestAppContext) {
  624    init_test(cx, |_| {});
  625
  626    let (text, selection_ranges) = marked_text_ranges(
  627        indoc! {"
  628            one
  629            two
  630            threeˇ
  631            four
  632            fiveˇ
  633        "},
  634        true,
  635    );
  636
  637    let editor = cx.add_window(|window, cx| {
  638        let buffer = MultiBuffer::build_simple(&text, cx);
  639        build_editor(buffer, window, cx)
  640    });
  641
  642    _ = editor.update(cx, |editor, window, cx| {
  643        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  644            s.select_ranges(selection_ranges.clone())
  645        });
  646        editor.fold_creases(
  647            vec![
  648                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  649                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  650            ],
  651            true,
  652            window,
  653            cx,
  654        );
  655    });
  656
  657    let cloned_editor = editor
  658        .update(cx, |editor, _, cx| {
  659            cx.open_window(Default::default(), |window, cx| {
  660                cx.new(|cx| editor.clone(window, cx))
  661            })
  662        })
  663        .unwrap()
  664        .unwrap();
  665
  666    let snapshot = editor
  667        .update(cx, |e, window, cx| e.snapshot(window, cx))
  668        .unwrap();
  669    let cloned_snapshot = cloned_editor
  670        .update(cx, |e, window, cx| e.snapshot(window, cx))
  671        .unwrap();
  672
  673    assert_eq!(
  674        cloned_editor
  675            .update(cx, |e, _, cx| e.display_text(cx))
  676            .unwrap(),
  677        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  678    );
  679    assert_eq!(
  680        cloned_snapshot
  681            .folds_in_range(0..text.len())
  682            .collect::<Vec<_>>(),
  683        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  684    );
  685    assert_set_eq!(
  686        cloned_editor
  687            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  688            .unwrap(),
  689        editor
  690            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  691            .unwrap()
  692    );
  693    assert_set_eq!(
  694        cloned_editor
  695            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  696            .unwrap(),
  697        editor
  698            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  699            .unwrap()
  700    );
  701}
  702
  703#[gpui::test]
  704async fn test_navigation_history(cx: &mut TestAppContext) {
  705    init_test(cx, |_| {});
  706
  707    use workspace::item::Item;
  708
  709    let fs = FakeFs::new(cx.executor());
  710    let project = Project::test(fs, [], cx).await;
  711    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  712    let pane = workspace
  713        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  714        .unwrap();
  715
  716    _ = workspace.update(cx, |_v, window, cx| {
  717        cx.new(|cx| {
  718            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  719            let mut editor = build_editor(buffer, window, cx);
  720            let handle = cx.entity();
  721            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  722
  723            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  724                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  725            }
  726
  727            // Move the cursor a small distance.
  728            // Nothing is added to the navigation history.
  729            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  730                s.select_display_ranges([
  731                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  732                ])
  733            });
  734            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  735                s.select_display_ranges([
  736                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  737                ])
  738            });
  739            assert!(pop_history(&mut editor, cx).is_none());
  740
  741            // Move the cursor a large distance.
  742            // The history can jump back to the previous position.
  743            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  744                s.select_display_ranges([
  745                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  746                ])
  747            });
  748            let nav_entry = pop_history(&mut editor, cx).unwrap();
  749            editor.navigate(nav_entry.data.unwrap(), window, cx);
  750            assert_eq!(nav_entry.item.id(), cx.entity_id());
  751            assert_eq!(
  752                editor.selections.display_ranges(cx),
  753                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  754            );
  755            assert!(pop_history(&mut editor, cx).is_none());
  756
  757            // Move the cursor a small distance via the mouse.
  758            // Nothing is added to the navigation history.
  759            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  760            editor.end_selection(window, cx);
  761            assert_eq!(
  762                editor.selections.display_ranges(cx),
  763                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  764            );
  765            assert!(pop_history(&mut editor, cx).is_none());
  766
  767            // Move the cursor a large distance via the mouse.
  768            // The history can jump back to the previous position.
  769            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  770            editor.end_selection(window, cx);
  771            assert_eq!(
  772                editor.selections.display_ranges(cx),
  773                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  774            );
  775            let nav_entry = pop_history(&mut editor, cx).unwrap();
  776            editor.navigate(nav_entry.data.unwrap(), window, cx);
  777            assert_eq!(nav_entry.item.id(), cx.entity_id());
  778            assert_eq!(
  779                editor.selections.display_ranges(cx),
  780                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  781            );
  782            assert!(pop_history(&mut editor, cx).is_none());
  783
  784            // Set scroll position to check later
  785            editor.set_scroll_position(gpui::Point::<f64>::new(5.5, 5.5), window, cx);
  786            let original_scroll_position = editor.scroll_manager.anchor();
  787
  788            // Jump to the end of the document and adjust scroll
  789            editor.move_to_end(&MoveToEnd, window, cx);
  790            editor.set_scroll_position(gpui::Point::<f64>::new(-2.5, -0.5), window, cx);
  791            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  792
  793            let nav_entry = pop_history(&mut editor, cx).unwrap();
  794            editor.navigate(nav_entry.data.unwrap(), window, cx);
  795            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  796
  797            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  798            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  799            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  800            let invalid_point = Point::new(9999, 0);
  801            editor.navigate(
  802                Box::new(NavigationData {
  803                    cursor_anchor: invalid_anchor,
  804                    cursor_position: invalid_point,
  805                    scroll_anchor: ScrollAnchor {
  806                        anchor: invalid_anchor,
  807                        offset: Default::default(),
  808                    },
  809                    scroll_top_row: invalid_point.row,
  810                }),
  811                window,
  812                cx,
  813            );
  814            assert_eq!(
  815                editor.selections.display_ranges(cx),
  816                &[editor.max_point(cx)..editor.max_point(cx)]
  817            );
  818            assert_eq!(
  819                editor.scroll_position(cx),
  820                gpui::Point::new(0., editor.max_point(cx).row().as_f64())
  821            );
  822
  823            editor
  824        })
  825    });
  826}
  827
  828#[gpui::test]
  829fn test_cancel(cx: &mut TestAppContext) {
  830    init_test(cx, |_| {});
  831
  832    let editor = cx.add_window(|window, cx| {
  833        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  834        build_editor(buffer, window, cx)
  835    });
  836
  837    _ = editor.update(cx, |editor, window, cx| {
  838        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  839        editor.update_selection(
  840            DisplayPoint::new(DisplayRow(1), 1),
  841            0,
  842            gpui::Point::<f32>::default(),
  843            window,
  844            cx,
  845        );
  846        editor.end_selection(window, cx);
  847
  848        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  849        editor.update_selection(
  850            DisplayPoint::new(DisplayRow(0), 3),
  851            0,
  852            gpui::Point::<f32>::default(),
  853            window,
  854            cx,
  855        );
  856        editor.end_selection(window, cx);
  857        assert_eq!(
  858            editor.selections.display_ranges(cx),
  859            [
  860                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  861                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  862            ]
  863        );
  864    });
  865
  866    _ = editor.update(cx, |editor, window, cx| {
  867        editor.cancel(&Cancel, window, cx);
  868        assert_eq!(
  869            editor.selections.display_ranges(cx),
  870            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  871        );
  872    });
  873
  874    _ = editor.update(cx, |editor, window, cx| {
  875        editor.cancel(&Cancel, window, cx);
  876        assert_eq!(
  877            editor.selections.display_ranges(cx),
  878            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  879        );
  880    });
  881}
  882
  883#[gpui::test]
  884fn test_fold_action(cx: &mut TestAppContext) {
  885    init_test(cx, |_| {});
  886
  887    let editor = cx.add_window(|window, cx| {
  888        let buffer = MultiBuffer::build_simple(
  889            &"
  890                impl Foo {
  891                    // Hello!
  892
  893                    fn a() {
  894                        1
  895                    }
  896
  897                    fn b() {
  898                        2
  899                    }
  900
  901                    fn c() {
  902                        3
  903                    }
  904                }
  905            "
  906            .unindent(),
  907            cx,
  908        );
  909        build_editor(buffer, window, cx)
  910    });
  911
  912    _ = editor.update(cx, |editor, window, cx| {
  913        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  914            s.select_display_ranges([
  915                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  916            ]);
  917        });
  918        editor.fold(&Fold, window, cx);
  919        assert_eq!(
  920            editor.display_text(cx),
  921            "
  922                impl Foo {
  923                    // Hello!
  924
  925                    fn a() {
  926                        1
  927                    }
  928
  929                    fn b() {⋯
  930                    }
  931
  932                    fn c() {⋯
  933                    }
  934                }
  935            "
  936            .unindent(),
  937        );
  938
  939        editor.fold(&Fold, window, cx);
  940        assert_eq!(
  941            editor.display_text(cx),
  942            "
  943                impl Foo {⋯
  944                }
  945            "
  946            .unindent(),
  947        );
  948
  949        editor.unfold_lines(&UnfoldLines, window, cx);
  950        assert_eq!(
  951            editor.display_text(cx),
  952            "
  953                impl Foo {
  954                    // Hello!
  955
  956                    fn a() {
  957                        1
  958                    }
  959
  960                    fn b() {⋯
  961                    }
  962
  963                    fn c() {⋯
  964                    }
  965                }
  966            "
  967            .unindent(),
  968        );
  969
  970        editor.unfold_lines(&UnfoldLines, window, cx);
  971        assert_eq!(
  972            editor.display_text(cx),
  973            editor.buffer.read(cx).read(cx).text()
  974        );
  975    });
  976}
  977
  978#[gpui::test]
  979fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  980    init_test(cx, |_| {});
  981
  982    let editor = cx.add_window(|window, cx| {
  983        let buffer = MultiBuffer::build_simple(
  984            &"
  985                class Foo:
  986                    # Hello!
  987
  988                    def a():
  989                        print(1)
  990
  991                    def b():
  992                        print(2)
  993
  994                    def c():
  995                        print(3)
  996            "
  997            .unindent(),
  998            cx,
  999        );
 1000        build_editor(buffer, window, cx)
 1001    });
 1002
 1003    _ = editor.update(cx, |editor, window, cx| {
 1004        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1005            s.select_display_ranges([
 1006                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1007            ]);
 1008        });
 1009        editor.fold(&Fold, window, cx);
 1010        assert_eq!(
 1011            editor.display_text(cx),
 1012            "
 1013                class Foo:
 1014                    # Hello!
 1015
 1016                    def a():
 1017                        print(1)
 1018
 1019                    def b():⋯
 1020
 1021                    def c():⋯
 1022            "
 1023            .unindent(),
 1024        );
 1025
 1026        editor.fold(&Fold, window, cx);
 1027        assert_eq!(
 1028            editor.display_text(cx),
 1029            "
 1030                class Foo:⋯
 1031            "
 1032            .unindent(),
 1033        );
 1034
 1035        editor.unfold_lines(&UnfoldLines, window, cx);
 1036        assert_eq!(
 1037            editor.display_text(cx),
 1038            "
 1039                class Foo:
 1040                    # Hello!
 1041
 1042                    def a():
 1043                        print(1)
 1044
 1045                    def b():⋯
 1046
 1047                    def c():⋯
 1048            "
 1049            .unindent(),
 1050        );
 1051
 1052        editor.unfold_lines(&UnfoldLines, window, cx);
 1053        assert_eq!(
 1054            editor.display_text(cx),
 1055            editor.buffer.read(cx).read(cx).text()
 1056        );
 1057    });
 1058}
 1059
 1060#[gpui::test]
 1061fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1062    init_test(cx, |_| {});
 1063
 1064    let editor = cx.add_window(|window, cx| {
 1065        let buffer = MultiBuffer::build_simple(
 1066            &"
 1067                class Foo:
 1068                    # Hello!
 1069
 1070                    def a():
 1071                        print(1)
 1072
 1073                    def b():
 1074                        print(2)
 1075
 1076
 1077                    def c():
 1078                        print(3)
 1079
 1080
 1081            "
 1082            .unindent(),
 1083            cx,
 1084        );
 1085        build_editor(buffer, window, cx)
 1086    });
 1087
 1088    _ = editor.update(cx, |editor, window, cx| {
 1089        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1090            s.select_display_ranges([
 1091                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1092            ]);
 1093        });
 1094        editor.fold(&Fold, window, cx);
 1095        assert_eq!(
 1096            editor.display_text(cx),
 1097            "
 1098                class Foo:
 1099                    # Hello!
 1100
 1101                    def a():
 1102                        print(1)
 1103
 1104                    def b():⋯
 1105
 1106
 1107                    def c():⋯
 1108
 1109
 1110            "
 1111            .unindent(),
 1112        );
 1113
 1114        editor.fold(&Fold, window, cx);
 1115        assert_eq!(
 1116            editor.display_text(cx),
 1117            "
 1118                class Foo:⋯
 1119
 1120
 1121            "
 1122            .unindent(),
 1123        );
 1124
 1125        editor.unfold_lines(&UnfoldLines, window, cx);
 1126        assert_eq!(
 1127            editor.display_text(cx),
 1128            "
 1129                class Foo:
 1130                    # Hello!
 1131
 1132                    def a():
 1133                        print(1)
 1134
 1135                    def b():⋯
 1136
 1137
 1138                    def c():⋯
 1139
 1140
 1141            "
 1142            .unindent(),
 1143        );
 1144
 1145        editor.unfold_lines(&UnfoldLines, window, cx);
 1146        assert_eq!(
 1147            editor.display_text(cx),
 1148            editor.buffer.read(cx).read(cx).text()
 1149        );
 1150    });
 1151}
 1152
 1153#[gpui::test]
 1154fn test_fold_at_level(cx: &mut TestAppContext) {
 1155    init_test(cx, |_| {});
 1156
 1157    let editor = cx.add_window(|window, cx| {
 1158        let buffer = MultiBuffer::build_simple(
 1159            &"
 1160                class Foo:
 1161                    # Hello!
 1162
 1163                    def a():
 1164                        print(1)
 1165
 1166                    def b():
 1167                        print(2)
 1168
 1169
 1170                class Bar:
 1171                    # World!
 1172
 1173                    def a():
 1174                        print(1)
 1175
 1176                    def b():
 1177                        print(2)
 1178
 1179
 1180            "
 1181            .unindent(),
 1182            cx,
 1183        );
 1184        build_editor(buffer, window, cx)
 1185    });
 1186
 1187    _ = editor.update(cx, |editor, window, cx| {
 1188        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1189        assert_eq!(
 1190            editor.display_text(cx),
 1191            "
 1192                class Foo:
 1193                    # Hello!
 1194
 1195                    def a():⋯
 1196
 1197                    def b():⋯
 1198
 1199
 1200                class Bar:
 1201                    # World!
 1202
 1203                    def a():⋯
 1204
 1205                    def b():⋯
 1206
 1207
 1208            "
 1209            .unindent(),
 1210        );
 1211
 1212        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1213        assert_eq!(
 1214            editor.display_text(cx),
 1215            "
 1216                class Foo:⋯
 1217
 1218
 1219                class Bar:⋯
 1220
 1221
 1222            "
 1223            .unindent(),
 1224        );
 1225
 1226        editor.unfold_all(&UnfoldAll, window, cx);
 1227        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1228        assert_eq!(
 1229            editor.display_text(cx),
 1230            "
 1231                class Foo:
 1232                    # Hello!
 1233
 1234                    def a():
 1235                        print(1)
 1236
 1237                    def b():
 1238                        print(2)
 1239
 1240
 1241                class Bar:
 1242                    # World!
 1243
 1244                    def a():
 1245                        print(1)
 1246
 1247                    def b():
 1248                        print(2)
 1249
 1250
 1251            "
 1252            .unindent(),
 1253        );
 1254
 1255        assert_eq!(
 1256            editor.display_text(cx),
 1257            editor.buffer.read(cx).read(cx).text()
 1258        );
 1259    });
 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::CodeAction("code-action-1".into()),
11878            Formatter::CodeAction("code-action-2".into()),
11879        ])))
11880    });
11881
11882    let fs = FakeFs::new(cx.executor());
11883    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
11884        .await;
11885
11886    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11887    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11888    language_registry.add(rust_lang());
11889
11890    let mut fake_servers = language_registry.register_fake_lsp(
11891        "Rust",
11892        FakeLspAdapter {
11893            capabilities: lsp::ServerCapabilities {
11894                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11895                execute_command_provider: Some(lsp::ExecuteCommandOptions {
11896                    commands: vec!["the-command-for-code-action-1".into()],
11897                    ..Default::default()
11898                }),
11899                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
11900                ..Default::default()
11901            },
11902            ..Default::default()
11903        },
11904    );
11905
11906    let buffer = project
11907        .update(cx, |project, cx| {
11908            project.open_local_buffer(path!("/file.rs"), cx)
11909        })
11910        .await
11911        .unwrap();
11912
11913    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11914    let (editor, cx) = cx.add_window_view(|window, cx| {
11915        build_editor_with_project(project.clone(), buffer, window, cx)
11916    });
11917
11918    cx.executor().start_waiting();
11919
11920    let fake_server = fake_servers.next().await.unwrap();
11921    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11922        move |_params, _| async move {
11923            Ok(Some(vec![lsp::TextEdit::new(
11924                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11925                "applied-formatting\n".to_string(),
11926            )]))
11927        },
11928    );
11929    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
11930        move |params, _| async move {
11931            let requested_code_actions = params.context.only.expect("Expected code action request");
11932            assert_eq!(requested_code_actions.len(), 1);
11933
11934            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
11935            let code_action = match requested_code_actions[0].as_str() {
11936                "code-action-1" => lsp::CodeAction {
11937                    kind: Some("code-action-1".into()),
11938                    edit: Some(lsp::WorkspaceEdit::new(
11939                        [(
11940                            uri,
11941                            vec![lsp::TextEdit::new(
11942                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11943                                "applied-code-action-1-edit\n".to_string(),
11944                            )],
11945                        )]
11946                        .into_iter()
11947                        .collect(),
11948                    )),
11949                    command: Some(lsp::Command {
11950                        command: "the-command-for-code-action-1".into(),
11951                        ..Default::default()
11952                    }),
11953                    ..Default::default()
11954                },
11955                "code-action-2" => lsp::CodeAction {
11956                    kind: Some("code-action-2".into()),
11957                    edit: Some(lsp::WorkspaceEdit::new(
11958                        [(
11959                            uri,
11960                            vec![lsp::TextEdit::new(
11961                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11962                                "applied-code-action-2-edit\n".to_string(),
11963                            )],
11964                        )]
11965                        .into_iter()
11966                        .collect(),
11967                    )),
11968                    ..Default::default()
11969                },
11970                req => panic!("Unexpected code action request: {:?}", req),
11971            };
11972            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
11973                code_action,
11974            )]))
11975        },
11976    );
11977
11978    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
11979        move |params, _| async move { Ok(params) }
11980    });
11981
11982    let command_lock = Arc::new(futures::lock::Mutex::new(()));
11983    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
11984        let fake = fake_server.clone();
11985        let lock = command_lock.clone();
11986        move |params, _| {
11987            assert_eq!(params.command, "the-command-for-code-action-1");
11988            let fake = fake.clone();
11989            let lock = lock.clone();
11990            async move {
11991                lock.lock().await;
11992                fake.server
11993                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
11994                        label: None,
11995                        edit: lsp::WorkspaceEdit {
11996                            changes: Some(
11997                                [(
11998                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
11999                                    vec![lsp::TextEdit {
12000                                        range: lsp::Range::new(
12001                                            lsp::Position::new(0, 0),
12002                                            lsp::Position::new(0, 0),
12003                                        ),
12004                                        new_text: "applied-code-action-1-command\n".into(),
12005                                    }],
12006                                )]
12007                                .into_iter()
12008                                .collect(),
12009                            ),
12010                            ..Default::default()
12011                        },
12012                    })
12013                    .await
12014                    .into_response()
12015                    .unwrap();
12016                Ok(Some(json!(null)))
12017            }
12018        }
12019    });
12020
12021    cx.executor().start_waiting();
12022    editor
12023        .update_in(cx, |editor, window, cx| {
12024            editor.perform_format(
12025                project.clone(),
12026                FormatTrigger::Manual,
12027                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12028                window,
12029                cx,
12030            )
12031        })
12032        .unwrap()
12033        .await;
12034    editor.update(cx, |editor, cx| {
12035        assert_eq!(
12036            editor.text(cx),
12037            r#"
12038                applied-code-action-2-edit
12039                applied-code-action-1-command
12040                applied-code-action-1-edit
12041                applied-formatting
12042                one
12043                two
12044                three
12045            "#
12046            .unindent()
12047        );
12048    });
12049
12050    editor.update_in(cx, |editor, window, cx| {
12051        editor.undo(&Default::default(), window, cx);
12052        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12053    });
12054
12055    // Perform a manual edit while waiting for an LSP command
12056    // that's being run as part of a formatting code action.
12057    let lock_guard = command_lock.lock().await;
12058    let format = editor
12059        .update_in(cx, |editor, window, cx| {
12060            editor.perform_format(
12061                project.clone(),
12062                FormatTrigger::Manual,
12063                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12064                window,
12065                cx,
12066            )
12067        })
12068        .unwrap();
12069    cx.run_until_parked();
12070    editor.update(cx, |editor, cx| {
12071        assert_eq!(
12072            editor.text(cx),
12073            r#"
12074                applied-code-action-1-edit
12075                applied-formatting
12076                one
12077                two
12078                three
12079            "#
12080            .unindent()
12081        );
12082
12083        editor.buffer.update(cx, |buffer, cx| {
12084            let ix = buffer.len(cx);
12085            buffer.edit([(ix..ix, "edited\n")], None, cx);
12086        });
12087    });
12088
12089    // Allow the LSP command to proceed. Because the buffer was edited,
12090    // the second code action will not be run.
12091    drop(lock_guard);
12092    format.await;
12093    editor.update_in(cx, |editor, window, cx| {
12094        assert_eq!(
12095            editor.text(cx),
12096            r#"
12097                applied-code-action-1-command
12098                applied-code-action-1-edit
12099                applied-formatting
12100                one
12101                two
12102                three
12103                edited
12104            "#
12105            .unindent()
12106        );
12107
12108        // The manual edit is undone first, because it is the last thing the user did
12109        // (even though the command completed afterwards).
12110        editor.undo(&Default::default(), window, cx);
12111        assert_eq!(
12112            editor.text(cx),
12113            r#"
12114                applied-code-action-1-command
12115                applied-code-action-1-edit
12116                applied-formatting
12117                one
12118                two
12119                three
12120            "#
12121            .unindent()
12122        );
12123
12124        // All the formatting (including the command, which completed after the manual edit)
12125        // is undone together.
12126        editor.undo(&Default::default(), window, cx);
12127        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12128    });
12129}
12130
12131#[gpui::test]
12132async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12133    init_test(cx, |settings| {
12134        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
12135            Formatter::LanguageServer { name: None },
12136        ])))
12137    });
12138
12139    let fs = FakeFs::new(cx.executor());
12140    fs.insert_file(path!("/file.ts"), Default::default()).await;
12141
12142    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12143
12144    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12145    language_registry.add(Arc::new(Language::new(
12146        LanguageConfig {
12147            name: "TypeScript".into(),
12148            matcher: LanguageMatcher {
12149                path_suffixes: vec!["ts".to_string()],
12150                ..Default::default()
12151            },
12152            ..LanguageConfig::default()
12153        },
12154        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12155    )));
12156    update_test_language_settings(cx, |settings| {
12157        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12158    });
12159    let mut fake_servers = language_registry.register_fake_lsp(
12160        "TypeScript",
12161        FakeLspAdapter {
12162            capabilities: lsp::ServerCapabilities {
12163                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12164                ..Default::default()
12165            },
12166            ..Default::default()
12167        },
12168    );
12169
12170    let buffer = project
12171        .update(cx, |project, cx| {
12172            project.open_local_buffer(path!("/file.ts"), cx)
12173        })
12174        .await
12175        .unwrap();
12176
12177    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12178    let (editor, cx) = cx.add_window_view(|window, cx| {
12179        build_editor_with_project(project.clone(), buffer, window, cx)
12180    });
12181    editor.update_in(cx, |editor, window, cx| {
12182        editor.set_text(
12183            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12184            window,
12185            cx,
12186        )
12187    });
12188
12189    cx.executor().start_waiting();
12190    let fake_server = fake_servers.next().await.unwrap();
12191
12192    let format = editor
12193        .update_in(cx, |editor, window, cx| {
12194            editor.perform_code_action_kind(
12195                project.clone(),
12196                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12197                window,
12198                cx,
12199            )
12200        })
12201        .unwrap();
12202    fake_server
12203        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12204            assert_eq!(
12205                params.text_document.uri,
12206                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12207            );
12208            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12209                lsp::CodeAction {
12210                    title: "Organize Imports".to_string(),
12211                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12212                    edit: Some(lsp::WorkspaceEdit {
12213                        changes: Some(
12214                            [(
12215                                params.text_document.uri.clone(),
12216                                vec![lsp::TextEdit::new(
12217                                    lsp::Range::new(
12218                                        lsp::Position::new(1, 0),
12219                                        lsp::Position::new(2, 0),
12220                                    ),
12221                                    "".to_string(),
12222                                )],
12223                            )]
12224                            .into_iter()
12225                            .collect(),
12226                        ),
12227                        ..Default::default()
12228                    }),
12229                    ..Default::default()
12230                },
12231            )]))
12232        })
12233        .next()
12234        .await;
12235    cx.executor().start_waiting();
12236    format.await;
12237    assert_eq!(
12238        editor.update(cx, |editor, cx| editor.text(cx)),
12239        "import { a } from 'module';\n\nconst x = a;\n"
12240    );
12241
12242    editor.update_in(cx, |editor, window, cx| {
12243        editor.set_text(
12244            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12245            window,
12246            cx,
12247        )
12248    });
12249    // Ensure we don't lock if code action hangs.
12250    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12251        move |params, _| async move {
12252            assert_eq!(
12253                params.text_document.uri,
12254                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12255            );
12256            futures::future::pending::<()>().await;
12257            unreachable!()
12258        },
12259    );
12260    let format = editor
12261        .update_in(cx, |editor, window, cx| {
12262            editor.perform_code_action_kind(
12263                project,
12264                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12265                window,
12266                cx,
12267            )
12268        })
12269        .unwrap();
12270    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
12271    cx.executor().start_waiting();
12272    format.await;
12273    assert_eq!(
12274        editor.update(cx, |editor, cx| editor.text(cx)),
12275        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
12276    );
12277}
12278
12279#[gpui::test]
12280async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
12281    init_test(cx, |_| {});
12282
12283    let mut cx = EditorLspTestContext::new_rust(
12284        lsp::ServerCapabilities {
12285            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12286            ..Default::default()
12287        },
12288        cx,
12289    )
12290    .await;
12291
12292    cx.set_state(indoc! {"
12293        one.twoˇ
12294    "});
12295
12296    // The format request takes a long time. When it completes, it inserts
12297    // a newline and an indent before the `.`
12298    cx.lsp
12299        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12300            let executor = cx.background_executor().clone();
12301            async move {
12302                executor.timer(Duration::from_millis(100)).await;
12303                Ok(Some(vec![lsp::TextEdit {
12304                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12305                    new_text: "\n    ".into(),
12306                }]))
12307            }
12308        });
12309
12310    // Submit a format request.
12311    let format_1 = cx
12312        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12313        .unwrap();
12314    cx.executor().run_until_parked();
12315
12316    // Submit a second format request.
12317    let format_2 = cx
12318        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12319        .unwrap();
12320    cx.executor().run_until_parked();
12321
12322    // Wait for both format requests to complete
12323    cx.executor().advance_clock(Duration::from_millis(200));
12324    cx.executor().start_waiting();
12325    format_1.await.unwrap();
12326    cx.executor().start_waiting();
12327    format_2.await.unwrap();
12328
12329    // The formatting edits only happens once.
12330    cx.assert_editor_state(indoc! {"
12331        one
12332            .twoˇ
12333    "});
12334}
12335
12336#[gpui::test]
12337async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12338    init_test(cx, |settings| {
12339        settings.defaults.formatter = Some(SelectedFormatter::Auto)
12340    });
12341
12342    let mut cx = EditorLspTestContext::new_rust(
12343        lsp::ServerCapabilities {
12344            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12345            ..Default::default()
12346        },
12347        cx,
12348    )
12349    .await;
12350
12351    // Set up a buffer white some trailing whitespace and no trailing newline.
12352    cx.set_state(
12353        &[
12354            "one ",   //
12355            "twoˇ",   //
12356            "three ", //
12357            "four",   //
12358        ]
12359        .join("\n"),
12360    );
12361
12362    // Submit a format request.
12363    let format = cx
12364        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12365        .unwrap();
12366
12367    // Record which buffer changes have been sent to the language server
12368    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12369    cx.lsp
12370        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12371            let buffer_changes = buffer_changes.clone();
12372            move |params, _| {
12373                buffer_changes.lock().extend(
12374                    params
12375                        .content_changes
12376                        .into_iter()
12377                        .map(|e| (e.range.unwrap(), e.text)),
12378                );
12379            }
12380        });
12381
12382    // Handle formatting requests to the language server.
12383    cx.lsp
12384        .set_request_handler::<lsp::request::Formatting, _, _>({
12385            let buffer_changes = buffer_changes.clone();
12386            move |_, _| {
12387                // When formatting is requested, trailing whitespace has already been stripped,
12388                // and the trailing newline has already been added.
12389                assert_eq!(
12390                    &buffer_changes.lock()[1..],
12391                    &[
12392                        (
12393                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12394                            "".into()
12395                        ),
12396                        (
12397                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12398                            "".into()
12399                        ),
12400                        (
12401                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12402                            "\n".into()
12403                        ),
12404                    ]
12405                );
12406
12407                // Insert blank lines between each line of the buffer.
12408                async move {
12409                    Ok(Some(vec![
12410                        lsp::TextEdit {
12411                            range: lsp::Range::new(
12412                                lsp::Position::new(1, 0),
12413                                lsp::Position::new(1, 0),
12414                            ),
12415                            new_text: "\n".into(),
12416                        },
12417                        lsp::TextEdit {
12418                            range: lsp::Range::new(
12419                                lsp::Position::new(2, 0),
12420                                lsp::Position::new(2, 0),
12421                            ),
12422                            new_text: "\n".into(),
12423                        },
12424                    ]))
12425                }
12426            }
12427        });
12428
12429    // After formatting the buffer, the trailing whitespace is stripped,
12430    // a newline is appended, and the edits provided by the language server
12431    // have been applied.
12432    format.await.unwrap();
12433    cx.assert_editor_state(
12434        &[
12435            "one",   //
12436            "",      //
12437            "twoˇ",  //
12438            "",      //
12439            "three", //
12440            "four",  //
12441            "",      //
12442        ]
12443        .join("\n"),
12444    );
12445
12446    // Undoing the formatting undoes the trailing whitespace removal, the
12447    // trailing newline, and the LSP edits.
12448    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12449    cx.assert_editor_state(
12450        &[
12451            "one ",   //
12452            "twoˇ",   //
12453            "three ", //
12454            "four",   //
12455        ]
12456        .join("\n"),
12457    );
12458}
12459
12460#[gpui::test]
12461async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12462    cx: &mut TestAppContext,
12463) {
12464    init_test(cx, |_| {});
12465
12466    cx.update(|cx| {
12467        cx.update_global::<SettingsStore, _>(|settings, cx| {
12468            settings.update_user_settings(cx, |settings| {
12469                settings.editor.auto_signature_help = Some(true);
12470            });
12471        });
12472    });
12473
12474    let mut cx = EditorLspTestContext::new_rust(
12475        lsp::ServerCapabilities {
12476            signature_help_provider: Some(lsp::SignatureHelpOptions {
12477                ..Default::default()
12478            }),
12479            ..Default::default()
12480        },
12481        cx,
12482    )
12483    .await;
12484
12485    let language = Language::new(
12486        LanguageConfig {
12487            name: "Rust".into(),
12488            brackets: BracketPairConfig {
12489                pairs: vec![
12490                    BracketPair {
12491                        start: "{".to_string(),
12492                        end: "}".to_string(),
12493                        close: true,
12494                        surround: true,
12495                        newline: true,
12496                    },
12497                    BracketPair {
12498                        start: "(".to_string(),
12499                        end: ")".to_string(),
12500                        close: true,
12501                        surround: true,
12502                        newline: true,
12503                    },
12504                    BracketPair {
12505                        start: "/*".to_string(),
12506                        end: " */".to_string(),
12507                        close: true,
12508                        surround: true,
12509                        newline: true,
12510                    },
12511                    BracketPair {
12512                        start: "[".to_string(),
12513                        end: "]".to_string(),
12514                        close: false,
12515                        surround: false,
12516                        newline: true,
12517                    },
12518                    BracketPair {
12519                        start: "\"".to_string(),
12520                        end: "\"".to_string(),
12521                        close: true,
12522                        surround: true,
12523                        newline: false,
12524                    },
12525                    BracketPair {
12526                        start: "<".to_string(),
12527                        end: ">".to_string(),
12528                        close: false,
12529                        surround: true,
12530                        newline: true,
12531                    },
12532                ],
12533                ..Default::default()
12534            },
12535            autoclose_before: "})]".to_string(),
12536            ..Default::default()
12537        },
12538        Some(tree_sitter_rust::LANGUAGE.into()),
12539    );
12540    let language = Arc::new(language);
12541
12542    cx.language_registry().add(language.clone());
12543    cx.update_buffer(|buffer, cx| {
12544        buffer.set_language(Some(language), cx);
12545    });
12546
12547    cx.set_state(
12548        &r#"
12549            fn main() {
12550                sampleˇ
12551            }
12552        "#
12553        .unindent(),
12554    );
12555
12556    cx.update_editor(|editor, window, cx| {
12557        editor.handle_input("(", window, cx);
12558    });
12559    cx.assert_editor_state(
12560        &"
12561            fn main() {
12562                sample(ˇ)
12563            }
12564        "
12565        .unindent(),
12566    );
12567
12568    let mocked_response = lsp::SignatureHelp {
12569        signatures: vec![lsp::SignatureInformation {
12570            label: "fn sample(param1: u8, param2: u8)".to_string(),
12571            documentation: None,
12572            parameters: Some(vec![
12573                lsp::ParameterInformation {
12574                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12575                    documentation: None,
12576                },
12577                lsp::ParameterInformation {
12578                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12579                    documentation: None,
12580                },
12581            ]),
12582            active_parameter: None,
12583        }],
12584        active_signature: Some(0),
12585        active_parameter: Some(0),
12586    };
12587    handle_signature_help_request(&mut cx, mocked_response).await;
12588
12589    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12590        .await;
12591
12592    cx.editor(|editor, _, _| {
12593        let signature_help_state = editor.signature_help_state.popover().cloned();
12594        let signature = signature_help_state.unwrap();
12595        assert_eq!(
12596            signature.signatures[signature.current_signature].label,
12597            "fn sample(param1: u8, param2: u8)"
12598        );
12599    });
12600}
12601
12602#[gpui::test]
12603async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
12604    init_test(cx, |_| {});
12605
12606    cx.update(|cx| {
12607        cx.update_global::<SettingsStore, _>(|settings, cx| {
12608            settings.update_user_settings(cx, |settings| {
12609                settings.editor.auto_signature_help = Some(false);
12610                settings.editor.show_signature_help_after_edits = Some(false);
12611            });
12612        });
12613    });
12614
12615    let mut cx = EditorLspTestContext::new_rust(
12616        lsp::ServerCapabilities {
12617            signature_help_provider: Some(lsp::SignatureHelpOptions {
12618                ..Default::default()
12619            }),
12620            ..Default::default()
12621        },
12622        cx,
12623    )
12624    .await;
12625
12626    let language = Language::new(
12627        LanguageConfig {
12628            name: "Rust".into(),
12629            brackets: BracketPairConfig {
12630                pairs: vec![
12631                    BracketPair {
12632                        start: "{".to_string(),
12633                        end: "}".to_string(),
12634                        close: true,
12635                        surround: true,
12636                        newline: true,
12637                    },
12638                    BracketPair {
12639                        start: "(".to_string(),
12640                        end: ")".to_string(),
12641                        close: true,
12642                        surround: true,
12643                        newline: true,
12644                    },
12645                    BracketPair {
12646                        start: "/*".to_string(),
12647                        end: " */".to_string(),
12648                        close: true,
12649                        surround: true,
12650                        newline: true,
12651                    },
12652                    BracketPair {
12653                        start: "[".to_string(),
12654                        end: "]".to_string(),
12655                        close: false,
12656                        surround: false,
12657                        newline: true,
12658                    },
12659                    BracketPair {
12660                        start: "\"".to_string(),
12661                        end: "\"".to_string(),
12662                        close: true,
12663                        surround: true,
12664                        newline: false,
12665                    },
12666                    BracketPair {
12667                        start: "<".to_string(),
12668                        end: ">".to_string(),
12669                        close: false,
12670                        surround: true,
12671                        newline: true,
12672                    },
12673                ],
12674                ..Default::default()
12675            },
12676            autoclose_before: "})]".to_string(),
12677            ..Default::default()
12678        },
12679        Some(tree_sitter_rust::LANGUAGE.into()),
12680    );
12681    let language = Arc::new(language);
12682
12683    cx.language_registry().add(language.clone());
12684    cx.update_buffer(|buffer, cx| {
12685        buffer.set_language(Some(language), cx);
12686    });
12687
12688    // Ensure that signature_help is not called when no signature help is enabled.
12689    cx.set_state(
12690        &r#"
12691            fn main() {
12692                sampleˇ
12693            }
12694        "#
12695        .unindent(),
12696    );
12697    cx.update_editor(|editor, window, cx| {
12698        editor.handle_input("(", window, cx);
12699    });
12700    cx.assert_editor_state(
12701        &"
12702            fn main() {
12703                sample(ˇ)
12704            }
12705        "
12706        .unindent(),
12707    );
12708    cx.editor(|editor, _, _| {
12709        assert!(editor.signature_help_state.task().is_none());
12710    });
12711
12712    let mocked_response = lsp::SignatureHelp {
12713        signatures: vec![lsp::SignatureInformation {
12714            label: "fn sample(param1: u8, param2: u8)".to_string(),
12715            documentation: None,
12716            parameters: Some(vec![
12717                lsp::ParameterInformation {
12718                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12719                    documentation: None,
12720                },
12721                lsp::ParameterInformation {
12722                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12723                    documentation: None,
12724                },
12725            ]),
12726            active_parameter: None,
12727        }],
12728        active_signature: Some(0),
12729        active_parameter: Some(0),
12730    };
12731
12732    // Ensure that signature_help is called when enabled afte edits
12733    cx.update(|_, cx| {
12734        cx.update_global::<SettingsStore, _>(|settings, cx| {
12735            settings.update_user_settings(cx, |settings| {
12736                settings.editor.auto_signature_help = Some(false);
12737                settings.editor.show_signature_help_after_edits = Some(true);
12738            });
12739        });
12740    });
12741    cx.set_state(
12742        &r#"
12743            fn main() {
12744                sampleˇ
12745            }
12746        "#
12747        .unindent(),
12748    );
12749    cx.update_editor(|editor, window, cx| {
12750        editor.handle_input("(", window, cx);
12751    });
12752    cx.assert_editor_state(
12753        &"
12754            fn main() {
12755                sample(ˇ)
12756            }
12757        "
12758        .unindent(),
12759    );
12760    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12761    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12762        .await;
12763    cx.update_editor(|editor, _, _| {
12764        let signature_help_state = editor.signature_help_state.popover().cloned();
12765        assert!(signature_help_state.is_some());
12766        let signature = signature_help_state.unwrap();
12767        assert_eq!(
12768            signature.signatures[signature.current_signature].label,
12769            "fn sample(param1: u8, param2: u8)"
12770        );
12771        editor.signature_help_state = SignatureHelpState::default();
12772    });
12773
12774    // Ensure that signature_help is called when auto signature help override is enabled
12775    cx.update(|_, cx| {
12776        cx.update_global::<SettingsStore, _>(|settings, cx| {
12777            settings.update_user_settings(cx, |settings| {
12778                settings.editor.auto_signature_help = Some(true);
12779                settings.editor.show_signature_help_after_edits = Some(false);
12780            });
12781        });
12782    });
12783    cx.set_state(
12784        &r#"
12785            fn main() {
12786                sampleˇ
12787            }
12788        "#
12789        .unindent(),
12790    );
12791    cx.update_editor(|editor, window, cx| {
12792        editor.handle_input("(", window, cx);
12793    });
12794    cx.assert_editor_state(
12795        &"
12796            fn main() {
12797                sample(ˇ)
12798            }
12799        "
12800        .unindent(),
12801    );
12802    handle_signature_help_request(&mut cx, mocked_response).await;
12803    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12804        .await;
12805    cx.editor(|editor, _, _| {
12806        let signature_help_state = editor.signature_help_state.popover().cloned();
12807        assert!(signature_help_state.is_some());
12808        let signature = signature_help_state.unwrap();
12809        assert_eq!(
12810            signature.signatures[signature.current_signature].label,
12811            "fn sample(param1: u8, param2: u8)"
12812        );
12813    });
12814}
12815
12816#[gpui::test]
12817async fn test_signature_help(cx: &mut TestAppContext) {
12818    init_test(cx, |_| {});
12819    cx.update(|cx| {
12820        cx.update_global::<SettingsStore, _>(|settings, cx| {
12821            settings.update_user_settings(cx, |settings| {
12822                settings.editor.auto_signature_help = Some(true);
12823            });
12824        });
12825    });
12826
12827    let mut cx = EditorLspTestContext::new_rust(
12828        lsp::ServerCapabilities {
12829            signature_help_provider: Some(lsp::SignatureHelpOptions {
12830                ..Default::default()
12831            }),
12832            ..Default::default()
12833        },
12834        cx,
12835    )
12836    .await;
12837
12838    // A test that directly calls `show_signature_help`
12839    cx.update_editor(|editor, window, cx| {
12840        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12841    });
12842
12843    let mocked_response = lsp::SignatureHelp {
12844        signatures: vec![lsp::SignatureInformation {
12845            label: "fn sample(param1: u8, param2: u8)".to_string(),
12846            documentation: None,
12847            parameters: Some(vec![
12848                lsp::ParameterInformation {
12849                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12850                    documentation: None,
12851                },
12852                lsp::ParameterInformation {
12853                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12854                    documentation: None,
12855                },
12856            ]),
12857            active_parameter: None,
12858        }],
12859        active_signature: Some(0),
12860        active_parameter: Some(0),
12861    };
12862    handle_signature_help_request(&mut cx, mocked_response).await;
12863
12864    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12865        .await;
12866
12867    cx.editor(|editor, _, _| {
12868        let signature_help_state = editor.signature_help_state.popover().cloned();
12869        assert!(signature_help_state.is_some());
12870        let signature = signature_help_state.unwrap();
12871        assert_eq!(
12872            signature.signatures[signature.current_signature].label,
12873            "fn sample(param1: u8, param2: u8)"
12874        );
12875    });
12876
12877    // When exiting outside from inside the brackets, `signature_help` is closed.
12878    cx.set_state(indoc! {"
12879        fn main() {
12880            sample(ˇ);
12881        }
12882
12883        fn sample(param1: u8, param2: u8) {}
12884    "});
12885
12886    cx.update_editor(|editor, window, cx| {
12887        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12888            s.select_ranges([0..0])
12889        });
12890    });
12891
12892    let mocked_response = lsp::SignatureHelp {
12893        signatures: Vec::new(),
12894        active_signature: None,
12895        active_parameter: None,
12896    };
12897    handle_signature_help_request(&mut cx, mocked_response).await;
12898
12899    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
12900        .await;
12901
12902    cx.editor(|editor, _, _| {
12903        assert!(!editor.signature_help_state.is_shown());
12904    });
12905
12906    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
12907    cx.set_state(indoc! {"
12908        fn main() {
12909            sample(ˇ);
12910        }
12911
12912        fn sample(param1: u8, param2: u8) {}
12913    "});
12914
12915    let mocked_response = lsp::SignatureHelp {
12916        signatures: vec![lsp::SignatureInformation {
12917            label: "fn sample(param1: u8, param2: u8)".to_string(),
12918            documentation: None,
12919            parameters: Some(vec![
12920                lsp::ParameterInformation {
12921                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12922                    documentation: None,
12923                },
12924                lsp::ParameterInformation {
12925                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12926                    documentation: None,
12927                },
12928            ]),
12929            active_parameter: None,
12930        }],
12931        active_signature: Some(0),
12932        active_parameter: Some(0),
12933    };
12934    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12935    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12936        .await;
12937    cx.editor(|editor, _, _| {
12938        assert!(editor.signature_help_state.is_shown());
12939    });
12940
12941    // Restore the popover with more parameter input
12942    cx.set_state(indoc! {"
12943        fn main() {
12944            sample(param1, param2ˇ);
12945        }
12946
12947        fn sample(param1: u8, param2: u8) {}
12948    "});
12949
12950    let mocked_response = lsp::SignatureHelp {
12951        signatures: vec![lsp::SignatureInformation {
12952            label: "fn sample(param1: u8, param2: u8)".to_string(),
12953            documentation: None,
12954            parameters: Some(vec![
12955                lsp::ParameterInformation {
12956                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12957                    documentation: None,
12958                },
12959                lsp::ParameterInformation {
12960                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12961                    documentation: None,
12962                },
12963            ]),
12964            active_parameter: None,
12965        }],
12966        active_signature: Some(0),
12967        active_parameter: Some(1),
12968    };
12969    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12970    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12971        .await;
12972
12973    // When selecting a range, the popover is gone.
12974    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
12975    cx.update_editor(|editor, window, cx| {
12976        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12977            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
12978        })
12979    });
12980    cx.assert_editor_state(indoc! {"
12981        fn main() {
12982            sample(param1, «ˇparam2»);
12983        }
12984
12985        fn sample(param1: u8, param2: u8) {}
12986    "});
12987    cx.editor(|editor, _, _| {
12988        assert!(!editor.signature_help_state.is_shown());
12989    });
12990
12991    // When unselecting again, the popover is back if within the brackets.
12992    cx.update_editor(|editor, window, cx| {
12993        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12994            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
12995        })
12996    });
12997    cx.assert_editor_state(indoc! {"
12998        fn main() {
12999            sample(param1, ˇparam2);
13000        }
13001
13002        fn sample(param1: u8, param2: u8) {}
13003    "});
13004    handle_signature_help_request(&mut cx, mocked_response).await;
13005    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13006        .await;
13007    cx.editor(|editor, _, _| {
13008        assert!(editor.signature_help_state.is_shown());
13009    });
13010
13011    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13012    cx.update_editor(|editor, window, cx| {
13013        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13014            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13015            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13016        })
13017    });
13018    cx.assert_editor_state(indoc! {"
13019        fn main() {
13020            sample(param1, ˇparam2);
13021        }
13022
13023        fn sample(param1: u8, param2: u8) {}
13024    "});
13025
13026    let mocked_response = lsp::SignatureHelp {
13027        signatures: vec![lsp::SignatureInformation {
13028            label: "fn sample(param1: u8, param2: u8)".to_string(),
13029            documentation: None,
13030            parameters: Some(vec![
13031                lsp::ParameterInformation {
13032                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13033                    documentation: None,
13034                },
13035                lsp::ParameterInformation {
13036                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13037                    documentation: None,
13038                },
13039            ]),
13040            active_parameter: None,
13041        }],
13042        active_signature: Some(0),
13043        active_parameter: Some(1),
13044    };
13045    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13046    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13047        .await;
13048    cx.update_editor(|editor, _, cx| {
13049        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13050    });
13051    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13052        .await;
13053    cx.update_editor(|editor, window, cx| {
13054        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13055            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13056        })
13057    });
13058    cx.assert_editor_state(indoc! {"
13059        fn main() {
13060            sample(param1, «ˇparam2»);
13061        }
13062
13063        fn sample(param1: u8, param2: u8) {}
13064    "});
13065    cx.update_editor(|editor, window, cx| {
13066        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13067            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13068        })
13069    });
13070    cx.assert_editor_state(indoc! {"
13071        fn main() {
13072            sample(param1, ˇparam2);
13073        }
13074
13075        fn sample(param1: u8, param2: u8) {}
13076    "});
13077    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13078        .await;
13079}
13080
13081#[gpui::test]
13082async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13083    init_test(cx, |_| {});
13084
13085    let mut cx = EditorLspTestContext::new_rust(
13086        lsp::ServerCapabilities {
13087            signature_help_provider: Some(lsp::SignatureHelpOptions {
13088                ..Default::default()
13089            }),
13090            ..Default::default()
13091        },
13092        cx,
13093    )
13094    .await;
13095
13096    cx.set_state(indoc! {"
13097        fn main() {
13098            overloadedˇ
13099        }
13100    "});
13101
13102    cx.update_editor(|editor, window, cx| {
13103        editor.handle_input("(", window, cx);
13104        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13105    });
13106
13107    // Mock response with 3 signatures
13108    let mocked_response = lsp::SignatureHelp {
13109        signatures: vec![
13110            lsp::SignatureInformation {
13111                label: "fn overloaded(x: i32)".to_string(),
13112                documentation: None,
13113                parameters: Some(vec![lsp::ParameterInformation {
13114                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13115                    documentation: None,
13116                }]),
13117                active_parameter: None,
13118            },
13119            lsp::SignatureInformation {
13120                label: "fn overloaded(x: i32, y: i32)".to_string(),
13121                documentation: None,
13122                parameters: Some(vec![
13123                    lsp::ParameterInformation {
13124                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13125                        documentation: None,
13126                    },
13127                    lsp::ParameterInformation {
13128                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13129                        documentation: None,
13130                    },
13131                ]),
13132                active_parameter: None,
13133            },
13134            lsp::SignatureInformation {
13135                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13136                documentation: None,
13137                parameters: Some(vec![
13138                    lsp::ParameterInformation {
13139                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13140                        documentation: None,
13141                    },
13142                    lsp::ParameterInformation {
13143                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13144                        documentation: None,
13145                    },
13146                    lsp::ParameterInformation {
13147                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13148                        documentation: None,
13149                    },
13150                ]),
13151                active_parameter: None,
13152            },
13153        ],
13154        active_signature: Some(1),
13155        active_parameter: Some(0),
13156    };
13157    handle_signature_help_request(&mut cx, mocked_response).await;
13158
13159    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13160        .await;
13161
13162    // Verify we have multiple signatures and the right one is selected
13163    cx.editor(|editor, _, _| {
13164        let popover = editor.signature_help_state.popover().cloned().unwrap();
13165        assert_eq!(popover.signatures.len(), 3);
13166        // active_signature was 1, so that should be the current
13167        assert_eq!(popover.current_signature, 1);
13168        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13169        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13170        assert_eq!(
13171            popover.signatures[2].label,
13172            "fn overloaded(x: i32, y: i32, z: i32)"
13173        );
13174    });
13175
13176    // Test navigation functionality
13177    cx.update_editor(|editor, window, cx| {
13178        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13179    });
13180
13181    cx.editor(|editor, _, _| {
13182        let popover = editor.signature_help_state.popover().cloned().unwrap();
13183        assert_eq!(popover.current_signature, 2);
13184    });
13185
13186    // Test wrap around
13187    cx.update_editor(|editor, window, cx| {
13188        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13189    });
13190
13191    cx.editor(|editor, _, _| {
13192        let popover = editor.signature_help_state.popover().cloned().unwrap();
13193        assert_eq!(popover.current_signature, 0);
13194    });
13195
13196    // Test previous navigation
13197    cx.update_editor(|editor, window, cx| {
13198        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13199    });
13200
13201    cx.editor(|editor, _, _| {
13202        let popover = editor.signature_help_state.popover().cloned().unwrap();
13203        assert_eq!(popover.current_signature, 2);
13204    });
13205}
13206
13207#[gpui::test]
13208async fn test_completion_mode(cx: &mut TestAppContext) {
13209    init_test(cx, |_| {});
13210    let mut cx = EditorLspTestContext::new_rust(
13211        lsp::ServerCapabilities {
13212            completion_provider: Some(lsp::CompletionOptions {
13213                resolve_provider: Some(true),
13214                ..Default::default()
13215            }),
13216            ..Default::default()
13217        },
13218        cx,
13219    )
13220    .await;
13221
13222    struct Run {
13223        run_description: &'static str,
13224        initial_state: String,
13225        buffer_marked_text: String,
13226        completion_label: &'static str,
13227        completion_text: &'static str,
13228        expected_with_insert_mode: String,
13229        expected_with_replace_mode: String,
13230        expected_with_replace_subsequence_mode: String,
13231        expected_with_replace_suffix_mode: String,
13232    }
13233
13234    let runs = [
13235        Run {
13236            run_description: "Start of word matches completion text",
13237            initial_state: "before ediˇ after".into(),
13238            buffer_marked_text: "before <edi|> after".into(),
13239            completion_label: "editor",
13240            completion_text: "editor",
13241            expected_with_insert_mode: "before editorˇ after".into(),
13242            expected_with_replace_mode: "before editorˇ after".into(),
13243            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13244            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13245        },
13246        Run {
13247            run_description: "Accept same text at the middle of the word",
13248            initial_state: "before ediˇtor after".into(),
13249            buffer_marked_text: "before <edi|tor> after".into(),
13250            completion_label: "editor",
13251            completion_text: "editor",
13252            expected_with_insert_mode: "before editorˇtor after".into(),
13253            expected_with_replace_mode: "before editorˇ after".into(),
13254            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13255            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13256        },
13257        Run {
13258            run_description: "End of word matches completion text -- cursor at end",
13259            initial_state: "before torˇ after".into(),
13260            buffer_marked_text: "before <tor|> after".into(),
13261            completion_label: "editor",
13262            completion_text: "editor",
13263            expected_with_insert_mode: "before editorˇ after".into(),
13264            expected_with_replace_mode: "before editorˇ after".into(),
13265            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13266            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13267        },
13268        Run {
13269            run_description: "End of word matches completion text -- cursor at start",
13270            initial_state: "before ˇtor after".into(),
13271            buffer_marked_text: "before <|tor> after".into(),
13272            completion_label: "editor",
13273            completion_text: "editor",
13274            expected_with_insert_mode: "before editorˇtor after".into(),
13275            expected_with_replace_mode: "before editorˇ after".into(),
13276            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13277            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13278        },
13279        Run {
13280            run_description: "Prepend text containing whitespace",
13281            initial_state: "pˇfield: bool".into(),
13282            buffer_marked_text: "<p|field>: bool".into(),
13283            completion_label: "pub ",
13284            completion_text: "pub ",
13285            expected_with_insert_mode: "pub ˇfield: bool".into(),
13286            expected_with_replace_mode: "pub ˇ: bool".into(),
13287            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13288            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13289        },
13290        Run {
13291            run_description: "Add element to start of list",
13292            initial_state: "[element_ˇelement_2]".into(),
13293            buffer_marked_text: "[<element_|element_2>]".into(),
13294            completion_label: "element_1",
13295            completion_text: "element_1",
13296            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13297            expected_with_replace_mode: "[element_1ˇ]".into(),
13298            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13299            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13300        },
13301        Run {
13302            run_description: "Add element to start of list -- first and second elements are equal",
13303            initial_state: "[elˇelement]".into(),
13304            buffer_marked_text: "[<el|element>]".into(),
13305            completion_label: "element",
13306            completion_text: "element",
13307            expected_with_insert_mode: "[elementˇelement]".into(),
13308            expected_with_replace_mode: "[elementˇ]".into(),
13309            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13310            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13311        },
13312        Run {
13313            run_description: "Ends with matching suffix",
13314            initial_state: "SubˇError".into(),
13315            buffer_marked_text: "<Sub|Error>".into(),
13316            completion_label: "SubscriptionError",
13317            completion_text: "SubscriptionError",
13318            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13319            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13320            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13321            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13322        },
13323        Run {
13324            run_description: "Suffix is a subsequence -- contiguous",
13325            initial_state: "SubˇErr".into(),
13326            buffer_marked_text: "<Sub|Err>".into(),
13327            completion_label: "SubscriptionError",
13328            completion_text: "SubscriptionError",
13329            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13330            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13331            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13332            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13333        },
13334        Run {
13335            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13336            initial_state: "Suˇscrirr".into(),
13337            buffer_marked_text: "<Su|scrirr>".into(),
13338            completion_label: "SubscriptionError",
13339            completion_text: "SubscriptionError",
13340            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13341            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13342            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13343            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13344        },
13345        Run {
13346            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13347            initial_state: "foo(indˇix)".into(),
13348            buffer_marked_text: "foo(<ind|ix>)".into(),
13349            completion_label: "node_index",
13350            completion_text: "node_index",
13351            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13352            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13353            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13354            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13355        },
13356        Run {
13357            run_description: "Replace range ends before cursor - should extend to cursor",
13358            initial_state: "before editˇo after".into(),
13359            buffer_marked_text: "before <{ed}>it|o after".into(),
13360            completion_label: "editor",
13361            completion_text: "editor",
13362            expected_with_insert_mode: "before editorˇo after".into(),
13363            expected_with_replace_mode: "before editorˇo after".into(),
13364            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13365            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13366        },
13367        Run {
13368            run_description: "Uses label for suffix matching",
13369            initial_state: "before ediˇtor after".into(),
13370            buffer_marked_text: "before <edi|tor> after".into(),
13371            completion_label: "editor",
13372            completion_text: "editor()",
13373            expected_with_insert_mode: "before editor()ˇtor after".into(),
13374            expected_with_replace_mode: "before editor()ˇ after".into(),
13375            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13376            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13377        },
13378        Run {
13379            run_description: "Case insensitive subsequence and suffix matching",
13380            initial_state: "before EDiˇtoR after".into(),
13381            buffer_marked_text: "before <EDi|toR> after".into(),
13382            completion_label: "editor",
13383            completion_text: "editor",
13384            expected_with_insert_mode: "before editorˇtoR after".into(),
13385            expected_with_replace_mode: "before editorˇ after".into(),
13386            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13387            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13388        },
13389    ];
13390
13391    for run in runs {
13392        let run_variations = [
13393            (LspInsertMode::Insert, run.expected_with_insert_mode),
13394            (LspInsertMode::Replace, run.expected_with_replace_mode),
13395            (
13396                LspInsertMode::ReplaceSubsequence,
13397                run.expected_with_replace_subsequence_mode,
13398            ),
13399            (
13400                LspInsertMode::ReplaceSuffix,
13401                run.expected_with_replace_suffix_mode,
13402            ),
13403        ];
13404
13405        for (lsp_insert_mode, expected_text) in run_variations {
13406            eprintln!(
13407                "run = {:?}, mode = {lsp_insert_mode:.?}",
13408                run.run_description,
13409            );
13410
13411            update_test_language_settings(&mut cx, |settings| {
13412                settings.defaults.completions = Some(CompletionSettingsContent {
13413                    lsp_insert_mode: Some(lsp_insert_mode),
13414                    words: Some(WordsCompletionMode::Disabled),
13415                    words_min_length: Some(0),
13416                    ..Default::default()
13417                });
13418            });
13419
13420            cx.set_state(&run.initial_state);
13421            cx.update_editor(|editor, window, cx| {
13422                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13423            });
13424
13425            let counter = Arc::new(AtomicUsize::new(0));
13426            handle_completion_request_with_insert_and_replace(
13427                &mut cx,
13428                &run.buffer_marked_text,
13429                vec![(run.completion_label, run.completion_text)],
13430                counter.clone(),
13431            )
13432            .await;
13433            cx.condition(|editor, _| editor.context_menu_visible())
13434                .await;
13435            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13436
13437            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13438                editor
13439                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13440                    .unwrap()
13441            });
13442            cx.assert_editor_state(&expected_text);
13443            handle_resolve_completion_request(&mut cx, None).await;
13444            apply_additional_edits.await.unwrap();
13445        }
13446    }
13447}
13448
13449#[gpui::test]
13450async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13451    init_test(cx, |_| {});
13452    let mut cx = EditorLspTestContext::new_rust(
13453        lsp::ServerCapabilities {
13454            completion_provider: Some(lsp::CompletionOptions {
13455                resolve_provider: Some(true),
13456                ..Default::default()
13457            }),
13458            ..Default::default()
13459        },
13460        cx,
13461    )
13462    .await;
13463
13464    let initial_state = "SubˇError";
13465    let buffer_marked_text = "<Sub|Error>";
13466    let completion_text = "SubscriptionError";
13467    let expected_with_insert_mode = "SubscriptionErrorˇError";
13468    let expected_with_replace_mode = "SubscriptionErrorˇ";
13469
13470    update_test_language_settings(&mut cx, |settings| {
13471        settings.defaults.completions = Some(CompletionSettingsContent {
13472            words: Some(WordsCompletionMode::Disabled),
13473            words_min_length: Some(0),
13474            // set the opposite here to ensure that the action is overriding the default behavior
13475            lsp_insert_mode: Some(LspInsertMode::Insert),
13476            ..Default::default()
13477        });
13478    });
13479
13480    cx.set_state(initial_state);
13481    cx.update_editor(|editor, window, cx| {
13482        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13483    });
13484
13485    let counter = Arc::new(AtomicUsize::new(0));
13486    handle_completion_request_with_insert_and_replace(
13487        &mut cx,
13488        buffer_marked_text,
13489        vec![(completion_text, completion_text)],
13490        counter.clone(),
13491    )
13492    .await;
13493    cx.condition(|editor, _| editor.context_menu_visible())
13494        .await;
13495    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13496
13497    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13498        editor
13499            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13500            .unwrap()
13501    });
13502    cx.assert_editor_state(expected_with_replace_mode);
13503    handle_resolve_completion_request(&mut cx, None).await;
13504    apply_additional_edits.await.unwrap();
13505
13506    update_test_language_settings(&mut cx, |settings| {
13507        settings.defaults.completions = Some(CompletionSettingsContent {
13508            words: Some(WordsCompletionMode::Disabled),
13509            words_min_length: Some(0),
13510            // set the opposite here to ensure that the action is overriding the default behavior
13511            lsp_insert_mode: Some(LspInsertMode::Replace),
13512            ..Default::default()
13513        });
13514    });
13515
13516    cx.set_state(initial_state);
13517    cx.update_editor(|editor, window, cx| {
13518        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13519    });
13520    handle_completion_request_with_insert_and_replace(
13521        &mut cx,
13522        buffer_marked_text,
13523        vec![(completion_text, completion_text)],
13524        counter.clone(),
13525    )
13526    .await;
13527    cx.condition(|editor, _| editor.context_menu_visible())
13528        .await;
13529    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13530
13531    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13532        editor
13533            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13534            .unwrap()
13535    });
13536    cx.assert_editor_state(expected_with_insert_mode);
13537    handle_resolve_completion_request(&mut cx, None).await;
13538    apply_additional_edits.await.unwrap();
13539}
13540
13541#[gpui::test]
13542async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
13543    init_test(cx, |_| {});
13544    let mut cx = EditorLspTestContext::new_rust(
13545        lsp::ServerCapabilities {
13546            completion_provider: Some(lsp::CompletionOptions {
13547                resolve_provider: Some(true),
13548                ..Default::default()
13549            }),
13550            ..Default::default()
13551        },
13552        cx,
13553    )
13554    .await;
13555
13556    // scenario: surrounding text matches completion text
13557    let completion_text = "to_offset";
13558    let initial_state = indoc! {"
13559        1. buf.to_offˇsuffix
13560        2. buf.to_offˇsuf
13561        3. buf.to_offˇfix
13562        4. buf.to_offˇ
13563        5. into_offˇensive
13564        6. ˇsuffix
13565        7. let ˇ //
13566        8. aaˇzz
13567        9. buf.to_off«zzzzzˇ»suffix
13568        10. buf.«ˇzzzzz»suffix
13569        11. to_off«ˇzzzzz»
13570
13571        buf.to_offˇsuffix  // newest cursor
13572    "};
13573    let completion_marked_buffer = indoc! {"
13574        1. buf.to_offsuffix
13575        2. buf.to_offsuf
13576        3. buf.to_offfix
13577        4. buf.to_off
13578        5. into_offensive
13579        6. suffix
13580        7. let  //
13581        8. aazz
13582        9. buf.to_offzzzzzsuffix
13583        10. buf.zzzzzsuffix
13584        11. to_offzzzzz
13585
13586        buf.<to_off|suffix>  // newest cursor
13587    "};
13588    let expected = indoc! {"
13589        1. buf.to_offsetˇ
13590        2. buf.to_offsetˇsuf
13591        3. buf.to_offsetˇfix
13592        4. buf.to_offsetˇ
13593        5. into_offsetˇensive
13594        6. to_offsetˇsuffix
13595        7. let to_offsetˇ //
13596        8. aato_offsetˇzz
13597        9. buf.to_offsetˇ
13598        10. buf.to_offsetˇsuffix
13599        11. to_offsetˇ
13600
13601        buf.to_offsetˇ  // newest cursor
13602    "};
13603    cx.set_state(initial_state);
13604    cx.update_editor(|editor, window, cx| {
13605        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13606    });
13607    handle_completion_request_with_insert_and_replace(
13608        &mut cx,
13609        completion_marked_buffer,
13610        vec![(completion_text, completion_text)],
13611        Arc::new(AtomicUsize::new(0)),
13612    )
13613    .await;
13614    cx.condition(|editor, _| editor.context_menu_visible())
13615        .await;
13616    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13617        editor
13618            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13619            .unwrap()
13620    });
13621    cx.assert_editor_state(expected);
13622    handle_resolve_completion_request(&mut cx, None).await;
13623    apply_additional_edits.await.unwrap();
13624
13625    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
13626    let completion_text = "foo_and_bar";
13627    let initial_state = indoc! {"
13628        1. ooanbˇ
13629        2. zooanbˇ
13630        3. ooanbˇz
13631        4. zooanbˇz
13632        5. ooanˇ
13633        6. oanbˇ
13634
13635        ooanbˇ
13636    "};
13637    let completion_marked_buffer = indoc! {"
13638        1. ooanb
13639        2. zooanb
13640        3. ooanbz
13641        4. zooanbz
13642        5. ooan
13643        6. oanb
13644
13645        <ooanb|>
13646    "};
13647    let expected = indoc! {"
13648        1. foo_and_barˇ
13649        2. zfoo_and_barˇ
13650        3. foo_and_barˇz
13651        4. zfoo_and_barˇz
13652        5. ooanfoo_and_barˇ
13653        6. oanbfoo_and_barˇ
13654
13655        foo_and_barˇ
13656    "};
13657    cx.set_state(initial_state);
13658    cx.update_editor(|editor, window, cx| {
13659        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13660    });
13661    handle_completion_request_with_insert_and_replace(
13662        &mut cx,
13663        completion_marked_buffer,
13664        vec![(completion_text, completion_text)],
13665        Arc::new(AtomicUsize::new(0)),
13666    )
13667    .await;
13668    cx.condition(|editor, _| editor.context_menu_visible())
13669        .await;
13670    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13671        editor
13672            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13673            .unwrap()
13674    });
13675    cx.assert_editor_state(expected);
13676    handle_resolve_completion_request(&mut cx, None).await;
13677    apply_additional_edits.await.unwrap();
13678
13679    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
13680    // (expects the same as if it was inserted at the end)
13681    let completion_text = "foo_and_bar";
13682    let initial_state = indoc! {"
13683        1. ooˇanb
13684        2. zooˇanb
13685        3. ooˇanbz
13686        4. zooˇanbz
13687
13688        ooˇanb
13689    "};
13690    let completion_marked_buffer = indoc! {"
13691        1. ooanb
13692        2. zooanb
13693        3. ooanbz
13694        4. zooanbz
13695
13696        <oo|anb>
13697    "};
13698    let expected = indoc! {"
13699        1. foo_and_barˇ
13700        2. zfoo_and_barˇ
13701        3. foo_and_barˇz
13702        4. zfoo_and_barˇz
13703
13704        foo_and_barˇ
13705    "};
13706    cx.set_state(initial_state);
13707    cx.update_editor(|editor, window, cx| {
13708        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13709    });
13710    handle_completion_request_with_insert_and_replace(
13711        &mut cx,
13712        completion_marked_buffer,
13713        vec![(completion_text, completion_text)],
13714        Arc::new(AtomicUsize::new(0)),
13715    )
13716    .await;
13717    cx.condition(|editor, _| editor.context_menu_visible())
13718        .await;
13719    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13720        editor
13721            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13722            .unwrap()
13723    });
13724    cx.assert_editor_state(expected);
13725    handle_resolve_completion_request(&mut cx, None).await;
13726    apply_additional_edits.await.unwrap();
13727}
13728
13729// This used to crash
13730#[gpui::test]
13731async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
13732    init_test(cx, |_| {});
13733
13734    let buffer_text = indoc! {"
13735        fn main() {
13736            10.satu;
13737
13738            //
13739            // separate cursors so they open in different excerpts (manually reproducible)
13740            //
13741
13742            10.satu20;
13743        }
13744    "};
13745    let multibuffer_text_with_selections = indoc! {"
13746        fn main() {
13747            10.satuˇ;
13748
13749            //
13750
13751            //
13752
13753            10.satuˇ20;
13754        }
13755    "};
13756    let expected_multibuffer = indoc! {"
13757        fn main() {
13758            10.saturating_sub()ˇ;
13759
13760            //
13761
13762            //
13763
13764            10.saturating_sub()ˇ;
13765        }
13766    "};
13767
13768    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
13769    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
13770
13771    let fs = FakeFs::new(cx.executor());
13772    fs.insert_tree(
13773        path!("/a"),
13774        json!({
13775            "main.rs": buffer_text,
13776        }),
13777    )
13778    .await;
13779
13780    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13781    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13782    language_registry.add(rust_lang());
13783    let mut fake_servers = language_registry.register_fake_lsp(
13784        "Rust",
13785        FakeLspAdapter {
13786            capabilities: lsp::ServerCapabilities {
13787                completion_provider: Some(lsp::CompletionOptions {
13788                    resolve_provider: None,
13789                    ..lsp::CompletionOptions::default()
13790                }),
13791                ..lsp::ServerCapabilities::default()
13792            },
13793            ..FakeLspAdapter::default()
13794        },
13795    );
13796    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13797    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13798    let buffer = project
13799        .update(cx, |project, cx| {
13800            project.open_local_buffer(path!("/a/main.rs"), cx)
13801        })
13802        .await
13803        .unwrap();
13804
13805    let multi_buffer = cx.new(|cx| {
13806        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
13807        multi_buffer.push_excerpts(
13808            buffer.clone(),
13809            [ExcerptRange::new(0..first_excerpt_end)],
13810            cx,
13811        );
13812        multi_buffer.push_excerpts(
13813            buffer.clone(),
13814            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
13815            cx,
13816        );
13817        multi_buffer
13818    });
13819
13820    let editor = workspace
13821        .update(cx, |_, window, cx| {
13822            cx.new(|cx| {
13823                Editor::new(
13824                    EditorMode::Full {
13825                        scale_ui_elements_with_buffer_font_size: false,
13826                        show_active_line_background: false,
13827                        sized_by_content: false,
13828                    },
13829                    multi_buffer.clone(),
13830                    Some(project.clone()),
13831                    window,
13832                    cx,
13833                )
13834            })
13835        })
13836        .unwrap();
13837
13838    let pane = workspace
13839        .update(cx, |workspace, _, _| workspace.active_pane().clone())
13840        .unwrap();
13841    pane.update_in(cx, |pane, window, cx| {
13842        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
13843    });
13844
13845    let fake_server = fake_servers.next().await.unwrap();
13846
13847    editor.update_in(cx, |editor, window, cx| {
13848        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13849            s.select_ranges([
13850                Point::new(1, 11)..Point::new(1, 11),
13851                Point::new(7, 11)..Point::new(7, 11),
13852            ])
13853        });
13854
13855        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
13856    });
13857
13858    editor.update_in(cx, |editor, window, cx| {
13859        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13860    });
13861
13862    fake_server
13863        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13864            let completion_item = lsp::CompletionItem {
13865                label: "saturating_sub()".into(),
13866                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
13867                    lsp::InsertReplaceEdit {
13868                        new_text: "saturating_sub()".to_owned(),
13869                        insert: lsp::Range::new(
13870                            lsp::Position::new(7, 7),
13871                            lsp::Position::new(7, 11),
13872                        ),
13873                        replace: lsp::Range::new(
13874                            lsp::Position::new(7, 7),
13875                            lsp::Position::new(7, 13),
13876                        ),
13877                    },
13878                )),
13879                ..lsp::CompletionItem::default()
13880            };
13881
13882            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
13883        })
13884        .next()
13885        .await
13886        .unwrap();
13887
13888    cx.condition(&editor, |editor, _| editor.context_menu_visible())
13889        .await;
13890
13891    editor
13892        .update_in(cx, |editor, window, cx| {
13893            editor
13894                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13895                .unwrap()
13896        })
13897        .await
13898        .unwrap();
13899
13900    editor.update(cx, |editor, cx| {
13901        assert_text_with_selections(editor, expected_multibuffer, cx);
13902    })
13903}
13904
13905#[gpui::test]
13906async fn test_completion(cx: &mut TestAppContext) {
13907    init_test(cx, |_| {});
13908
13909    let mut cx = EditorLspTestContext::new_rust(
13910        lsp::ServerCapabilities {
13911            completion_provider: Some(lsp::CompletionOptions {
13912                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13913                resolve_provider: Some(true),
13914                ..Default::default()
13915            }),
13916            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13917            ..Default::default()
13918        },
13919        cx,
13920    )
13921    .await;
13922    let counter = Arc::new(AtomicUsize::new(0));
13923
13924    cx.set_state(indoc! {"
13925        oneˇ
13926        two
13927        three
13928    "});
13929    cx.simulate_keystroke(".");
13930    handle_completion_request(
13931        indoc! {"
13932            one.|<>
13933            two
13934            three
13935        "},
13936        vec!["first_completion", "second_completion"],
13937        true,
13938        counter.clone(),
13939        &mut cx,
13940    )
13941    .await;
13942    cx.condition(|editor, _| editor.context_menu_visible())
13943        .await;
13944    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13945
13946    let _handler = handle_signature_help_request(
13947        &mut cx,
13948        lsp::SignatureHelp {
13949            signatures: vec![lsp::SignatureInformation {
13950                label: "test signature".to_string(),
13951                documentation: None,
13952                parameters: Some(vec![lsp::ParameterInformation {
13953                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
13954                    documentation: None,
13955                }]),
13956                active_parameter: None,
13957            }],
13958            active_signature: None,
13959            active_parameter: None,
13960        },
13961    );
13962    cx.update_editor(|editor, window, cx| {
13963        assert!(
13964            !editor.signature_help_state.is_shown(),
13965            "No signature help was called for"
13966        );
13967        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13968    });
13969    cx.run_until_parked();
13970    cx.update_editor(|editor, _, _| {
13971        assert!(
13972            !editor.signature_help_state.is_shown(),
13973            "No signature help should be shown when completions menu is open"
13974        );
13975    });
13976
13977    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13978        editor.context_menu_next(&Default::default(), window, cx);
13979        editor
13980            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13981            .unwrap()
13982    });
13983    cx.assert_editor_state(indoc! {"
13984        one.second_completionˇ
13985        two
13986        three
13987    "});
13988
13989    handle_resolve_completion_request(
13990        &mut cx,
13991        Some(vec![
13992            (
13993                //This overlaps with the primary completion edit which is
13994                //misbehavior from the LSP spec, test that we filter it out
13995                indoc! {"
13996                    one.second_ˇcompletion
13997                    two
13998                    threeˇ
13999                "},
14000                "overlapping additional edit",
14001            ),
14002            (
14003                indoc! {"
14004                    one.second_completion
14005                    two
14006                    threeˇ
14007                "},
14008                "\nadditional edit",
14009            ),
14010        ]),
14011    )
14012    .await;
14013    apply_additional_edits.await.unwrap();
14014    cx.assert_editor_state(indoc! {"
14015        one.second_completionˇ
14016        two
14017        three
14018        additional edit
14019    "});
14020
14021    cx.set_state(indoc! {"
14022        one.second_completion
14023        twoˇ
14024        threeˇ
14025        additional edit
14026    "});
14027    cx.simulate_keystroke(" ");
14028    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14029    cx.simulate_keystroke("s");
14030    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14031
14032    cx.assert_editor_state(indoc! {"
14033        one.second_completion
14034        two sˇ
14035        three sˇ
14036        additional edit
14037    "});
14038    handle_completion_request(
14039        indoc! {"
14040            one.second_completion
14041            two s
14042            three <s|>
14043            additional edit
14044        "},
14045        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14046        true,
14047        counter.clone(),
14048        &mut cx,
14049    )
14050    .await;
14051    cx.condition(|editor, _| editor.context_menu_visible())
14052        .await;
14053    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14054
14055    cx.simulate_keystroke("i");
14056
14057    handle_completion_request(
14058        indoc! {"
14059            one.second_completion
14060            two si
14061            three <si|>
14062            additional edit
14063        "},
14064        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14065        true,
14066        counter.clone(),
14067        &mut cx,
14068    )
14069    .await;
14070    cx.condition(|editor, _| editor.context_menu_visible())
14071        .await;
14072    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14073
14074    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14075        editor
14076            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14077            .unwrap()
14078    });
14079    cx.assert_editor_state(indoc! {"
14080        one.second_completion
14081        two sixth_completionˇ
14082        three sixth_completionˇ
14083        additional edit
14084    "});
14085
14086    apply_additional_edits.await.unwrap();
14087
14088    update_test_language_settings(&mut cx, |settings| {
14089        settings.defaults.show_completions_on_input = Some(false);
14090    });
14091    cx.set_state("editorˇ");
14092    cx.simulate_keystroke(".");
14093    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14094    cx.simulate_keystrokes("c l o");
14095    cx.assert_editor_state("editor.cloˇ");
14096    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14097    cx.update_editor(|editor, window, cx| {
14098        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14099    });
14100    handle_completion_request(
14101        "editor.<clo|>",
14102        vec!["close", "clobber"],
14103        true,
14104        counter.clone(),
14105        &mut cx,
14106    )
14107    .await;
14108    cx.condition(|editor, _| editor.context_menu_visible())
14109        .await;
14110    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14111
14112    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14113        editor
14114            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14115            .unwrap()
14116    });
14117    cx.assert_editor_state("editor.clobberˇ");
14118    handle_resolve_completion_request(&mut cx, None).await;
14119    apply_additional_edits.await.unwrap();
14120}
14121
14122#[gpui::test]
14123async fn test_completion_reuse(cx: &mut TestAppContext) {
14124    init_test(cx, |_| {});
14125
14126    let mut cx = EditorLspTestContext::new_rust(
14127        lsp::ServerCapabilities {
14128            completion_provider: Some(lsp::CompletionOptions {
14129                trigger_characters: Some(vec![".".to_string()]),
14130                ..Default::default()
14131            }),
14132            ..Default::default()
14133        },
14134        cx,
14135    )
14136    .await;
14137
14138    let counter = Arc::new(AtomicUsize::new(0));
14139    cx.set_state("objˇ");
14140    cx.simulate_keystroke(".");
14141
14142    // Initial completion request returns complete results
14143    let is_incomplete = false;
14144    handle_completion_request(
14145        "obj.|<>",
14146        vec!["a", "ab", "abc"],
14147        is_incomplete,
14148        counter.clone(),
14149        &mut cx,
14150    )
14151    .await;
14152    cx.run_until_parked();
14153    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14154    cx.assert_editor_state("obj.ˇ");
14155    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14156
14157    // Type "a" - filters existing completions
14158    cx.simulate_keystroke("a");
14159    cx.run_until_parked();
14160    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14161    cx.assert_editor_state("obj.aˇ");
14162    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14163
14164    // Type "b" - filters existing completions
14165    cx.simulate_keystroke("b");
14166    cx.run_until_parked();
14167    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14168    cx.assert_editor_state("obj.abˇ");
14169    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14170
14171    // Type "c" - filters existing completions
14172    cx.simulate_keystroke("c");
14173    cx.run_until_parked();
14174    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14175    cx.assert_editor_state("obj.abcˇ");
14176    check_displayed_completions(vec!["abc"], &mut cx);
14177
14178    // Backspace to delete "c" - filters existing completions
14179    cx.update_editor(|editor, window, cx| {
14180        editor.backspace(&Backspace, window, cx);
14181    });
14182    cx.run_until_parked();
14183    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14184    cx.assert_editor_state("obj.abˇ");
14185    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14186
14187    // Moving cursor to the left dismisses menu.
14188    cx.update_editor(|editor, window, cx| {
14189        editor.move_left(&MoveLeft, window, cx);
14190    });
14191    cx.run_until_parked();
14192    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14193    cx.assert_editor_state("obj.aˇb");
14194    cx.update_editor(|editor, _, _| {
14195        assert_eq!(editor.context_menu_visible(), false);
14196    });
14197
14198    // Type "b" - new request
14199    cx.simulate_keystroke("b");
14200    let is_incomplete = false;
14201    handle_completion_request(
14202        "obj.<ab|>a",
14203        vec!["ab", "abc"],
14204        is_incomplete,
14205        counter.clone(),
14206        &mut cx,
14207    )
14208    .await;
14209    cx.run_until_parked();
14210    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14211    cx.assert_editor_state("obj.abˇb");
14212    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14213
14214    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14215    cx.update_editor(|editor, window, cx| {
14216        editor.backspace(&Backspace, window, cx);
14217    });
14218    let is_incomplete = false;
14219    handle_completion_request(
14220        "obj.<a|>b",
14221        vec!["a", "ab", "abc"],
14222        is_incomplete,
14223        counter.clone(),
14224        &mut cx,
14225    )
14226    .await;
14227    cx.run_until_parked();
14228    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14229    cx.assert_editor_state("obj.aˇb");
14230    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14231
14232    // Backspace to delete "a" - dismisses menu.
14233    cx.update_editor(|editor, window, cx| {
14234        editor.backspace(&Backspace, window, cx);
14235    });
14236    cx.run_until_parked();
14237    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14238    cx.assert_editor_state("obj.ˇb");
14239    cx.update_editor(|editor, _, _| {
14240        assert_eq!(editor.context_menu_visible(), false);
14241    });
14242}
14243
14244#[gpui::test]
14245async fn test_word_completion(cx: &mut TestAppContext) {
14246    let lsp_fetch_timeout_ms = 10;
14247    init_test(cx, |language_settings| {
14248        language_settings.defaults.completions = Some(CompletionSettingsContent {
14249            words_min_length: Some(0),
14250            lsp_fetch_timeout_ms: Some(10),
14251            lsp_insert_mode: Some(LspInsertMode::Insert),
14252            ..Default::default()
14253        });
14254    });
14255
14256    let mut cx = EditorLspTestContext::new_rust(
14257        lsp::ServerCapabilities {
14258            completion_provider: Some(lsp::CompletionOptions {
14259                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14260                ..lsp::CompletionOptions::default()
14261            }),
14262            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14263            ..lsp::ServerCapabilities::default()
14264        },
14265        cx,
14266    )
14267    .await;
14268
14269    let throttle_completions = Arc::new(AtomicBool::new(false));
14270
14271    let lsp_throttle_completions = throttle_completions.clone();
14272    let _completion_requests_handler =
14273        cx.lsp
14274            .server
14275            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14276                let lsp_throttle_completions = lsp_throttle_completions.clone();
14277                let cx = cx.clone();
14278                async move {
14279                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14280                        cx.background_executor()
14281                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14282                            .await;
14283                    }
14284                    Ok(Some(lsp::CompletionResponse::Array(vec![
14285                        lsp::CompletionItem {
14286                            label: "first".into(),
14287                            ..lsp::CompletionItem::default()
14288                        },
14289                        lsp::CompletionItem {
14290                            label: "last".into(),
14291                            ..lsp::CompletionItem::default()
14292                        },
14293                    ])))
14294                }
14295            });
14296
14297    cx.set_state(indoc! {"
14298        oneˇ
14299        two
14300        three
14301    "});
14302    cx.simulate_keystroke(".");
14303    cx.executor().run_until_parked();
14304    cx.condition(|editor, _| editor.context_menu_visible())
14305        .await;
14306    cx.update_editor(|editor, window, cx| {
14307        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14308        {
14309            assert_eq!(
14310                completion_menu_entries(menu),
14311                &["first", "last"],
14312                "When LSP server is fast to reply, no fallback word completions are used"
14313            );
14314        } else {
14315            panic!("expected completion menu to be open");
14316        }
14317        editor.cancel(&Cancel, window, cx);
14318    });
14319    cx.executor().run_until_parked();
14320    cx.condition(|editor, _| !editor.context_menu_visible())
14321        .await;
14322
14323    throttle_completions.store(true, atomic::Ordering::Release);
14324    cx.simulate_keystroke(".");
14325    cx.executor()
14326        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14327    cx.executor().run_until_parked();
14328    cx.condition(|editor, _| editor.context_menu_visible())
14329        .await;
14330    cx.update_editor(|editor, _, _| {
14331        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14332        {
14333            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14334                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14335        } else {
14336            panic!("expected completion menu to be open");
14337        }
14338    });
14339}
14340
14341#[gpui::test]
14342async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14343    init_test(cx, |language_settings| {
14344        language_settings.defaults.completions = Some(CompletionSettingsContent {
14345            words: Some(WordsCompletionMode::Enabled),
14346            words_min_length: Some(0),
14347            lsp_insert_mode: Some(LspInsertMode::Insert),
14348            ..Default::default()
14349        });
14350    });
14351
14352    let mut cx = EditorLspTestContext::new_rust(
14353        lsp::ServerCapabilities {
14354            completion_provider: Some(lsp::CompletionOptions {
14355                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14356                ..lsp::CompletionOptions::default()
14357            }),
14358            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14359            ..lsp::ServerCapabilities::default()
14360        },
14361        cx,
14362    )
14363    .await;
14364
14365    let _completion_requests_handler =
14366        cx.lsp
14367            .server
14368            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14369                Ok(Some(lsp::CompletionResponse::Array(vec![
14370                    lsp::CompletionItem {
14371                        label: "first".into(),
14372                        ..lsp::CompletionItem::default()
14373                    },
14374                    lsp::CompletionItem {
14375                        label: "last".into(),
14376                        ..lsp::CompletionItem::default()
14377                    },
14378                ])))
14379            });
14380
14381    cx.set_state(indoc! {"ˇ
14382        first
14383        last
14384        second
14385    "});
14386    cx.simulate_keystroke(".");
14387    cx.executor().run_until_parked();
14388    cx.condition(|editor, _| editor.context_menu_visible())
14389        .await;
14390    cx.update_editor(|editor, _, _| {
14391        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14392        {
14393            assert_eq!(
14394                completion_menu_entries(menu),
14395                &["first", "last", "second"],
14396                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14397            );
14398        } else {
14399            panic!("expected completion menu to be open");
14400        }
14401    });
14402}
14403
14404#[gpui::test]
14405async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14406    init_test(cx, |language_settings| {
14407        language_settings.defaults.completions = Some(CompletionSettingsContent {
14408            words: Some(WordsCompletionMode::Disabled),
14409            words_min_length: Some(0),
14410            lsp_insert_mode: Some(LspInsertMode::Insert),
14411            ..Default::default()
14412        });
14413    });
14414
14415    let mut cx = EditorLspTestContext::new_rust(
14416        lsp::ServerCapabilities {
14417            completion_provider: Some(lsp::CompletionOptions {
14418                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14419                ..lsp::CompletionOptions::default()
14420            }),
14421            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14422            ..lsp::ServerCapabilities::default()
14423        },
14424        cx,
14425    )
14426    .await;
14427
14428    let _completion_requests_handler =
14429        cx.lsp
14430            .server
14431            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14432                panic!("LSP completions should not be queried when dealing with word completions")
14433            });
14434
14435    cx.set_state(indoc! {"ˇ
14436        first
14437        last
14438        second
14439    "});
14440    cx.update_editor(|editor, window, cx| {
14441        editor.show_word_completions(&ShowWordCompletions, window, cx);
14442    });
14443    cx.executor().run_until_parked();
14444    cx.condition(|editor, _| editor.context_menu_visible())
14445        .await;
14446    cx.update_editor(|editor, _, _| {
14447        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14448        {
14449            assert_eq!(
14450                completion_menu_entries(menu),
14451                &["first", "last", "second"],
14452                "`ShowWordCompletions` action should show word completions"
14453            );
14454        } else {
14455            panic!("expected completion menu to be open");
14456        }
14457    });
14458
14459    cx.simulate_keystroke("l");
14460    cx.executor().run_until_parked();
14461    cx.condition(|editor, _| editor.context_menu_visible())
14462        .await;
14463    cx.update_editor(|editor, _, _| {
14464        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14465        {
14466            assert_eq!(
14467                completion_menu_entries(menu),
14468                &["last"],
14469                "After showing word completions, further editing should filter them and not query the LSP"
14470            );
14471        } else {
14472            panic!("expected completion menu to be open");
14473        }
14474    });
14475}
14476
14477#[gpui::test]
14478async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14479    init_test(cx, |language_settings| {
14480        language_settings.defaults.completions = Some(CompletionSettingsContent {
14481            words_min_length: Some(0),
14482            lsp: Some(false),
14483            lsp_insert_mode: Some(LspInsertMode::Insert),
14484            ..Default::default()
14485        });
14486    });
14487
14488    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14489
14490    cx.set_state(indoc! {"ˇ
14491        0_usize
14492        let
14493        33
14494        4.5f32
14495    "});
14496    cx.update_editor(|editor, window, cx| {
14497        editor.show_completions(&ShowCompletions::default(), window, cx);
14498    });
14499    cx.executor().run_until_parked();
14500    cx.condition(|editor, _| editor.context_menu_visible())
14501        .await;
14502    cx.update_editor(|editor, window, cx| {
14503        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14504        {
14505            assert_eq!(
14506                completion_menu_entries(menu),
14507                &["let"],
14508                "With no digits in the completion query, no digits should be in the word completions"
14509            );
14510        } else {
14511            panic!("expected completion menu to be open");
14512        }
14513        editor.cancel(&Cancel, window, cx);
14514    });
14515
14516    cx.set_state(indoc! {"14517        0_usize
14518        let
14519        3
14520        33.35f32
14521    "});
14522    cx.update_editor(|editor, window, cx| {
14523        editor.show_completions(&ShowCompletions::default(), window, cx);
14524    });
14525    cx.executor().run_until_parked();
14526    cx.condition(|editor, _| editor.context_menu_visible())
14527        .await;
14528    cx.update_editor(|editor, _, _| {
14529        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14530        {
14531            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14532                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14533        } else {
14534            panic!("expected completion menu to be open");
14535        }
14536    });
14537}
14538
14539#[gpui::test]
14540async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
14541    init_test(cx, |language_settings| {
14542        language_settings.defaults.completions = Some(CompletionSettingsContent {
14543            words: Some(WordsCompletionMode::Enabled),
14544            words_min_length: Some(3),
14545            lsp_insert_mode: Some(LspInsertMode::Insert),
14546            ..Default::default()
14547        });
14548    });
14549
14550    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14551    cx.set_state(indoc! {"ˇ
14552        wow
14553        wowen
14554        wowser
14555    "});
14556    cx.simulate_keystroke("w");
14557    cx.executor().run_until_parked();
14558    cx.update_editor(|editor, _, _| {
14559        if editor.context_menu.borrow_mut().is_some() {
14560            panic!(
14561                "expected completion menu to be hidden, as words completion threshold is not met"
14562            );
14563        }
14564    });
14565
14566    cx.update_editor(|editor, window, cx| {
14567        editor.show_word_completions(&ShowWordCompletions, window, cx);
14568    });
14569    cx.executor().run_until_parked();
14570    cx.update_editor(|editor, window, cx| {
14571        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14572        {
14573            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");
14574        } else {
14575            panic!("expected completion menu to be open after the word completions are called with an action");
14576        }
14577
14578        editor.cancel(&Cancel, window, cx);
14579    });
14580    cx.update_editor(|editor, _, _| {
14581        if editor.context_menu.borrow_mut().is_some() {
14582            panic!("expected completion menu to be hidden after canceling");
14583        }
14584    });
14585
14586    cx.simulate_keystroke("o");
14587    cx.executor().run_until_parked();
14588    cx.update_editor(|editor, _, _| {
14589        if editor.context_menu.borrow_mut().is_some() {
14590            panic!(
14591                "expected completion menu to be hidden, as words completion threshold is not met still"
14592            );
14593        }
14594    });
14595
14596    cx.simulate_keystroke("w");
14597    cx.executor().run_until_parked();
14598    cx.update_editor(|editor, _, _| {
14599        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14600        {
14601            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
14602        } else {
14603            panic!("expected completion menu to be open after the word completions threshold is met");
14604        }
14605    });
14606}
14607
14608#[gpui::test]
14609async fn test_word_completions_disabled(cx: &mut TestAppContext) {
14610    init_test(cx, |language_settings| {
14611        language_settings.defaults.completions = Some(CompletionSettingsContent {
14612            words: Some(WordsCompletionMode::Enabled),
14613            words_min_length: Some(0),
14614            lsp_insert_mode: Some(LspInsertMode::Insert),
14615            ..Default::default()
14616        });
14617    });
14618
14619    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14620    cx.update_editor(|editor, _, _| {
14621        editor.disable_word_completions();
14622    });
14623    cx.set_state(indoc! {"ˇ
14624        wow
14625        wowen
14626        wowser
14627    "});
14628    cx.simulate_keystroke("w");
14629    cx.executor().run_until_parked();
14630    cx.update_editor(|editor, _, _| {
14631        if editor.context_menu.borrow_mut().is_some() {
14632            panic!(
14633                "expected completion menu to be hidden, as words completion are disabled for this editor"
14634            );
14635        }
14636    });
14637
14638    cx.update_editor(|editor, window, cx| {
14639        editor.show_word_completions(&ShowWordCompletions, window, cx);
14640    });
14641    cx.executor().run_until_parked();
14642    cx.update_editor(|editor, _, _| {
14643        if editor.context_menu.borrow_mut().is_some() {
14644            panic!(
14645                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
14646            );
14647        }
14648    });
14649}
14650
14651fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
14652    let position = || lsp::Position {
14653        line: params.text_document_position.position.line,
14654        character: params.text_document_position.position.character,
14655    };
14656    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14657        range: lsp::Range {
14658            start: position(),
14659            end: position(),
14660        },
14661        new_text: text.to_string(),
14662    }))
14663}
14664
14665#[gpui::test]
14666async fn test_multiline_completion(cx: &mut TestAppContext) {
14667    init_test(cx, |_| {});
14668
14669    let fs = FakeFs::new(cx.executor());
14670    fs.insert_tree(
14671        path!("/a"),
14672        json!({
14673            "main.ts": "a",
14674        }),
14675    )
14676    .await;
14677
14678    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14679    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14680    let typescript_language = Arc::new(Language::new(
14681        LanguageConfig {
14682            name: "TypeScript".into(),
14683            matcher: LanguageMatcher {
14684                path_suffixes: vec!["ts".to_string()],
14685                ..LanguageMatcher::default()
14686            },
14687            line_comments: vec!["// ".into()],
14688            ..LanguageConfig::default()
14689        },
14690        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14691    ));
14692    language_registry.add(typescript_language.clone());
14693    let mut fake_servers = language_registry.register_fake_lsp(
14694        "TypeScript",
14695        FakeLspAdapter {
14696            capabilities: lsp::ServerCapabilities {
14697                completion_provider: Some(lsp::CompletionOptions {
14698                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14699                    ..lsp::CompletionOptions::default()
14700                }),
14701                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14702                ..lsp::ServerCapabilities::default()
14703            },
14704            // Emulate vtsls label generation
14705            label_for_completion: Some(Box::new(|item, _| {
14706                let text = if let Some(description) = item
14707                    .label_details
14708                    .as_ref()
14709                    .and_then(|label_details| label_details.description.as_ref())
14710                {
14711                    format!("{} {}", item.label, description)
14712                } else if let Some(detail) = &item.detail {
14713                    format!("{} {}", item.label, detail)
14714                } else {
14715                    item.label.clone()
14716                };
14717                let len = text.len();
14718                Some(language::CodeLabel {
14719                    text,
14720                    runs: Vec::new(),
14721                    filter_range: 0..len,
14722                })
14723            })),
14724            ..FakeLspAdapter::default()
14725        },
14726    );
14727    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14728    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14729    let worktree_id = workspace
14730        .update(cx, |workspace, _window, cx| {
14731            workspace.project().update(cx, |project, cx| {
14732                project.worktrees(cx).next().unwrap().read(cx).id()
14733            })
14734        })
14735        .unwrap();
14736    let _buffer = project
14737        .update(cx, |project, cx| {
14738            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
14739        })
14740        .await
14741        .unwrap();
14742    let editor = workspace
14743        .update(cx, |workspace, window, cx| {
14744            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
14745        })
14746        .unwrap()
14747        .await
14748        .unwrap()
14749        .downcast::<Editor>()
14750        .unwrap();
14751    let fake_server = fake_servers.next().await.unwrap();
14752
14753    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
14754    let multiline_label_2 = "a\nb\nc\n";
14755    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
14756    let multiline_description = "d\ne\nf\n";
14757    let multiline_detail_2 = "g\nh\ni\n";
14758
14759    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
14760        move |params, _| async move {
14761            Ok(Some(lsp::CompletionResponse::Array(vec![
14762                lsp::CompletionItem {
14763                    label: multiline_label.to_string(),
14764                    text_edit: gen_text_edit(&params, "new_text_1"),
14765                    ..lsp::CompletionItem::default()
14766                },
14767                lsp::CompletionItem {
14768                    label: "single line label 1".to_string(),
14769                    detail: Some(multiline_detail.to_string()),
14770                    text_edit: gen_text_edit(&params, "new_text_2"),
14771                    ..lsp::CompletionItem::default()
14772                },
14773                lsp::CompletionItem {
14774                    label: "single line label 2".to_string(),
14775                    label_details: Some(lsp::CompletionItemLabelDetails {
14776                        description: Some(multiline_description.to_string()),
14777                        detail: None,
14778                    }),
14779                    text_edit: gen_text_edit(&params, "new_text_2"),
14780                    ..lsp::CompletionItem::default()
14781                },
14782                lsp::CompletionItem {
14783                    label: multiline_label_2.to_string(),
14784                    detail: Some(multiline_detail_2.to_string()),
14785                    text_edit: gen_text_edit(&params, "new_text_3"),
14786                    ..lsp::CompletionItem::default()
14787                },
14788                lsp::CompletionItem {
14789                    label: "Label with many     spaces and \t but without newlines".to_string(),
14790                    detail: Some(
14791                        "Details with many     spaces and \t but without newlines".to_string(),
14792                    ),
14793                    text_edit: gen_text_edit(&params, "new_text_4"),
14794                    ..lsp::CompletionItem::default()
14795                },
14796            ])))
14797        },
14798    );
14799
14800    editor.update_in(cx, |editor, window, cx| {
14801        cx.focus_self(window);
14802        editor.move_to_end(&MoveToEnd, window, cx);
14803        editor.handle_input(".", window, cx);
14804    });
14805    cx.run_until_parked();
14806    completion_handle.next().await.unwrap();
14807
14808    editor.update(cx, |editor, _| {
14809        assert!(editor.context_menu_visible());
14810        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14811        {
14812            let completion_labels = menu
14813                .completions
14814                .borrow()
14815                .iter()
14816                .map(|c| c.label.text.clone())
14817                .collect::<Vec<_>>();
14818            assert_eq!(
14819                completion_labels,
14820                &[
14821                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
14822                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
14823                    "single line label 2 d e f ",
14824                    "a b c g h i ",
14825                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
14826                ],
14827                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
14828            );
14829
14830            for completion in menu
14831                .completions
14832                .borrow()
14833                .iter() {
14834                    assert_eq!(
14835                        completion.label.filter_range,
14836                        0..completion.label.text.len(),
14837                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
14838                    );
14839                }
14840        } else {
14841            panic!("expected completion menu to be open");
14842        }
14843    });
14844}
14845
14846#[gpui::test]
14847async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
14848    init_test(cx, |_| {});
14849    let mut cx = EditorLspTestContext::new_rust(
14850        lsp::ServerCapabilities {
14851            completion_provider: Some(lsp::CompletionOptions {
14852                trigger_characters: Some(vec![".".to_string()]),
14853                ..Default::default()
14854            }),
14855            ..Default::default()
14856        },
14857        cx,
14858    )
14859    .await;
14860    cx.lsp
14861        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14862            Ok(Some(lsp::CompletionResponse::Array(vec![
14863                lsp::CompletionItem {
14864                    label: "first".into(),
14865                    ..Default::default()
14866                },
14867                lsp::CompletionItem {
14868                    label: "last".into(),
14869                    ..Default::default()
14870                },
14871            ])))
14872        });
14873    cx.set_state("variableˇ");
14874    cx.simulate_keystroke(".");
14875    cx.executor().run_until_parked();
14876
14877    cx.update_editor(|editor, _, _| {
14878        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14879        {
14880            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
14881        } else {
14882            panic!("expected completion menu to be open");
14883        }
14884    });
14885
14886    cx.update_editor(|editor, window, cx| {
14887        editor.move_page_down(&MovePageDown::default(), window, cx);
14888        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14889        {
14890            assert!(
14891                menu.selected_item == 1,
14892                "expected PageDown to select the last item from the context menu"
14893            );
14894        } else {
14895            panic!("expected completion menu to stay open after PageDown");
14896        }
14897    });
14898
14899    cx.update_editor(|editor, window, cx| {
14900        editor.move_page_up(&MovePageUp::default(), window, cx);
14901        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14902        {
14903            assert!(
14904                menu.selected_item == 0,
14905                "expected PageUp to select the first item from the context menu"
14906            );
14907        } else {
14908            panic!("expected completion menu to stay open after PageUp");
14909        }
14910    });
14911}
14912
14913#[gpui::test]
14914async fn test_as_is_completions(cx: &mut TestAppContext) {
14915    init_test(cx, |_| {});
14916    let mut cx = EditorLspTestContext::new_rust(
14917        lsp::ServerCapabilities {
14918            completion_provider: Some(lsp::CompletionOptions {
14919                ..Default::default()
14920            }),
14921            ..Default::default()
14922        },
14923        cx,
14924    )
14925    .await;
14926    cx.lsp
14927        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14928            Ok(Some(lsp::CompletionResponse::Array(vec![
14929                lsp::CompletionItem {
14930                    label: "unsafe".into(),
14931                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14932                        range: lsp::Range {
14933                            start: lsp::Position {
14934                                line: 1,
14935                                character: 2,
14936                            },
14937                            end: lsp::Position {
14938                                line: 1,
14939                                character: 3,
14940                            },
14941                        },
14942                        new_text: "unsafe".to_string(),
14943                    })),
14944                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
14945                    ..Default::default()
14946                },
14947            ])))
14948        });
14949    cx.set_state("fn a() {}\n");
14950    cx.executor().run_until_parked();
14951    cx.update_editor(|editor, window, cx| {
14952        editor.show_completions(
14953            &ShowCompletions {
14954                trigger: Some("\n".into()),
14955            },
14956            window,
14957            cx,
14958        );
14959    });
14960    cx.executor().run_until_parked();
14961
14962    cx.update_editor(|editor, window, cx| {
14963        editor.confirm_completion(&Default::default(), window, cx)
14964    });
14965    cx.executor().run_until_parked();
14966    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
14967}
14968
14969#[gpui::test]
14970async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
14971    init_test(cx, |_| {});
14972    let language =
14973        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
14974    let mut cx = EditorLspTestContext::new(
14975        language,
14976        lsp::ServerCapabilities {
14977            completion_provider: Some(lsp::CompletionOptions {
14978                ..lsp::CompletionOptions::default()
14979            }),
14980            ..lsp::ServerCapabilities::default()
14981        },
14982        cx,
14983    )
14984    .await;
14985
14986    cx.set_state(
14987        "#ifndef BAR_H
14988#define BAR_H
14989
14990#include <stdbool.h>
14991
14992int fn_branch(bool do_branch1, bool do_branch2);
14993
14994#endif // BAR_H
14995ˇ",
14996    );
14997    cx.executor().run_until_parked();
14998    cx.update_editor(|editor, window, cx| {
14999        editor.handle_input("#", window, cx);
15000    });
15001    cx.executor().run_until_parked();
15002    cx.update_editor(|editor, window, cx| {
15003        editor.handle_input("i", window, cx);
15004    });
15005    cx.executor().run_until_parked();
15006    cx.update_editor(|editor, window, cx| {
15007        editor.handle_input("n", window, cx);
15008    });
15009    cx.executor().run_until_parked();
15010    cx.assert_editor_state(
15011        "#ifndef BAR_H
15012#define BAR_H
15013
15014#include <stdbool.h>
15015
15016int fn_branch(bool do_branch1, bool do_branch2);
15017
15018#endif // BAR_H
15019#inˇ",
15020    );
15021
15022    cx.lsp
15023        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15024            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15025                is_incomplete: false,
15026                item_defaults: None,
15027                items: vec![lsp::CompletionItem {
15028                    kind: Some(lsp::CompletionItemKind::SNIPPET),
15029                    label_details: Some(lsp::CompletionItemLabelDetails {
15030                        detail: Some("header".to_string()),
15031                        description: None,
15032                    }),
15033                    label: " include".to_string(),
15034                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15035                        range: lsp::Range {
15036                            start: lsp::Position {
15037                                line: 8,
15038                                character: 1,
15039                            },
15040                            end: lsp::Position {
15041                                line: 8,
15042                                character: 1,
15043                            },
15044                        },
15045                        new_text: "include \"$0\"".to_string(),
15046                    })),
15047                    sort_text: Some("40b67681include".to_string()),
15048                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15049                    filter_text: Some("include".to_string()),
15050                    insert_text: Some("include \"$0\"".to_string()),
15051                    ..lsp::CompletionItem::default()
15052                }],
15053            })))
15054        });
15055    cx.update_editor(|editor, window, cx| {
15056        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15057    });
15058    cx.executor().run_until_parked();
15059    cx.update_editor(|editor, window, cx| {
15060        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15061    });
15062    cx.executor().run_until_parked();
15063    cx.assert_editor_state(
15064        "#ifndef BAR_H
15065#define BAR_H
15066
15067#include <stdbool.h>
15068
15069int fn_branch(bool do_branch1, bool do_branch2);
15070
15071#endif // BAR_H
15072#include \"ˇ\"",
15073    );
15074
15075    cx.lsp
15076        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15077            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15078                is_incomplete: true,
15079                item_defaults: None,
15080                items: vec![lsp::CompletionItem {
15081                    kind: Some(lsp::CompletionItemKind::FILE),
15082                    label: "AGL/".to_string(),
15083                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15084                        range: lsp::Range {
15085                            start: lsp::Position {
15086                                line: 8,
15087                                character: 10,
15088                            },
15089                            end: lsp::Position {
15090                                line: 8,
15091                                character: 11,
15092                            },
15093                        },
15094                        new_text: "AGL/".to_string(),
15095                    })),
15096                    sort_text: Some("40b67681AGL/".to_string()),
15097                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15098                    filter_text: Some("AGL/".to_string()),
15099                    insert_text: Some("AGL/".to_string()),
15100                    ..lsp::CompletionItem::default()
15101                }],
15102            })))
15103        });
15104    cx.update_editor(|editor, window, cx| {
15105        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15106    });
15107    cx.executor().run_until_parked();
15108    cx.update_editor(|editor, window, cx| {
15109        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15110    });
15111    cx.executor().run_until_parked();
15112    cx.assert_editor_state(
15113        r##"#ifndef BAR_H
15114#define BAR_H
15115
15116#include <stdbool.h>
15117
15118int fn_branch(bool do_branch1, bool do_branch2);
15119
15120#endif // BAR_H
15121#include "AGL/ˇ"##,
15122    );
15123
15124    cx.update_editor(|editor, window, cx| {
15125        editor.handle_input("\"", window, cx);
15126    });
15127    cx.executor().run_until_parked();
15128    cx.assert_editor_state(
15129        r##"#ifndef BAR_H
15130#define BAR_H
15131
15132#include <stdbool.h>
15133
15134int fn_branch(bool do_branch1, bool do_branch2);
15135
15136#endif // BAR_H
15137#include "AGL/"ˇ"##,
15138    );
15139}
15140
15141#[gpui::test]
15142async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15143    init_test(cx, |_| {});
15144
15145    let mut cx = EditorLspTestContext::new_rust(
15146        lsp::ServerCapabilities {
15147            completion_provider: Some(lsp::CompletionOptions {
15148                trigger_characters: Some(vec![".".to_string()]),
15149                resolve_provider: Some(true),
15150                ..Default::default()
15151            }),
15152            ..Default::default()
15153        },
15154        cx,
15155    )
15156    .await;
15157
15158    cx.set_state("fn main() { let a = 2ˇ; }");
15159    cx.simulate_keystroke(".");
15160    let completion_item = lsp::CompletionItem {
15161        label: "Some".into(),
15162        kind: Some(lsp::CompletionItemKind::SNIPPET),
15163        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15164        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15165            kind: lsp::MarkupKind::Markdown,
15166            value: "```rust\nSome(2)\n```".to_string(),
15167        })),
15168        deprecated: Some(false),
15169        sort_text: Some("Some".to_string()),
15170        filter_text: Some("Some".to_string()),
15171        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15172        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15173            range: lsp::Range {
15174                start: lsp::Position {
15175                    line: 0,
15176                    character: 22,
15177                },
15178                end: lsp::Position {
15179                    line: 0,
15180                    character: 22,
15181                },
15182            },
15183            new_text: "Some(2)".to_string(),
15184        })),
15185        additional_text_edits: Some(vec![lsp::TextEdit {
15186            range: lsp::Range {
15187                start: lsp::Position {
15188                    line: 0,
15189                    character: 20,
15190                },
15191                end: lsp::Position {
15192                    line: 0,
15193                    character: 22,
15194                },
15195            },
15196            new_text: "".to_string(),
15197        }]),
15198        ..Default::default()
15199    };
15200
15201    let closure_completion_item = completion_item.clone();
15202    let counter = Arc::new(AtomicUsize::new(0));
15203    let counter_clone = counter.clone();
15204    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15205        let task_completion_item = closure_completion_item.clone();
15206        counter_clone.fetch_add(1, atomic::Ordering::Release);
15207        async move {
15208            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15209                is_incomplete: true,
15210                item_defaults: None,
15211                items: vec![task_completion_item],
15212            })))
15213        }
15214    });
15215
15216    cx.condition(|editor, _| editor.context_menu_visible())
15217        .await;
15218    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15219    assert!(request.next().await.is_some());
15220    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15221
15222    cx.simulate_keystrokes("S o m");
15223    cx.condition(|editor, _| editor.context_menu_visible())
15224        .await;
15225    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15226    assert!(request.next().await.is_some());
15227    assert!(request.next().await.is_some());
15228    assert!(request.next().await.is_some());
15229    request.close();
15230    assert!(request.next().await.is_none());
15231    assert_eq!(
15232        counter.load(atomic::Ordering::Acquire),
15233        4,
15234        "With the completions menu open, only one LSP request should happen per input"
15235    );
15236}
15237
15238#[gpui::test]
15239async fn test_toggle_comment(cx: &mut TestAppContext) {
15240    init_test(cx, |_| {});
15241    let mut cx = EditorTestContext::new(cx).await;
15242    let language = Arc::new(Language::new(
15243        LanguageConfig {
15244            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15245            ..Default::default()
15246        },
15247        Some(tree_sitter_rust::LANGUAGE.into()),
15248    ));
15249    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15250
15251    // If multiple selections intersect a line, the line is only toggled once.
15252    cx.set_state(indoc! {"
15253        fn a() {
15254            «//b();
15255            ˇ»// «c();
15256            //ˇ»  d();
15257        }
15258    "});
15259
15260    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15261
15262    cx.assert_editor_state(indoc! {"
15263        fn a() {
15264            «b();
15265            c();
15266            ˇ» d();
15267        }
15268    "});
15269
15270    // The comment prefix is inserted at the same column for every line in a
15271    // selection.
15272    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15273
15274    cx.assert_editor_state(indoc! {"
15275        fn a() {
15276            // «b();
15277            // c();
15278            ˇ»//  d();
15279        }
15280    "});
15281
15282    // If a selection ends at the beginning of a line, that line is not toggled.
15283    cx.set_selections_state(indoc! {"
15284        fn a() {
15285            // b();
15286            «// c();
15287        ˇ»    //  d();
15288        }
15289    "});
15290
15291    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15292
15293    cx.assert_editor_state(indoc! {"
15294        fn a() {
15295            // b();
15296            «c();
15297        ˇ»    //  d();
15298        }
15299    "});
15300
15301    // If a selection span a single line and is empty, the line is toggled.
15302    cx.set_state(indoc! {"
15303        fn a() {
15304            a();
15305            b();
15306        ˇ
15307        }
15308    "});
15309
15310    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15311
15312    cx.assert_editor_state(indoc! {"
15313        fn a() {
15314            a();
15315            b();
15316        //•ˇ
15317        }
15318    "});
15319
15320    // If a selection span multiple lines, empty lines are not toggled.
15321    cx.set_state(indoc! {"
15322        fn a() {
15323            «a();
15324
15325            c();ˇ»
15326        }
15327    "});
15328
15329    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15330
15331    cx.assert_editor_state(indoc! {"
15332        fn a() {
15333            // «a();
15334
15335            // c();ˇ»
15336        }
15337    "});
15338
15339    // If a selection includes multiple comment prefixes, all lines are uncommented.
15340    cx.set_state(indoc! {"
15341        fn a() {
15342            «// a();
15343            /// b();
15344            //! c();ˇ»
15345        }
15346    "});
15347
15348    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15349
15350    cx.assert_editor_state(indoc! {"
15351        fn a() {
15352            «a();
15353            b();
15354            c();ˇ»
15355        }
15356    "});
15357}
15358
15359#[gpui::test]
15360async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15361    init_test(cx, |_| {});
15362    let mut cx = EditorTestContext::new(cx).await;
15363    let language = Arc::new(Language::new(
15364        LanguageConfig {
15365            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15366            ..Default::default()
15367        },
15368        Some(tree_sitter_rust::LANGUAGE.into()),
15369    ));
15370    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15371
15372    let toggle_comments = &ToggleComments {
15373        advance_downwards: false,
15374        ignore_indent: true,
15375    };
15376
15377    // If multiple selections intersect a line, the line is only toggled once.
15378    cx.set_state(indoc! {"
15379        fn a() {
15380        //    «b();
15381        //    c();
15382        //    ˇ» d();
15383        }
15384    "});
15385
15386    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15387
15388    cx.assert_editor_state(indoc! {"
15389        fn a() {
15390            «b();
15391            c();
15392            ˇ» d();
15393        }
15394    "});
15395
15396    // The comment prefix is inserted at the beginning of each line
15397    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15398
15399    cx.assert_editor_state(indoc! {"
15400        fn a() {
15401        //    «b();
15402        //    c();
15403        //    ˇ» d();
15404        }
15405    "});
15406
15407    // If a selection ends at the beginning of a line, that line is not toggled.
15408    cx.set_selections_state(indoc! {"
15409        fn a() {
15410        //    b();
15411        //    «c();
15412        ˇ»//     d();
15413        }
15414    "});
15415
15416    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15417
15418    cx.assert_editor_state(indoc! {"
15419        fn a() {
15420        //    b();
15421            «c();
15422        ˇ»//     d();
15423        }
15424    "});
15425
15426    // If a selection span a single line and is empty, the line is toggled.
15427    cx.set_state(indoc! {"
15428        fn a() {
15429            a();
15430            b();
15431        ˇ
15432        }
15433    "});
15434
15435    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15436
15437    cx.assert_editor_state(indoc! {"
15438        fn a() {
15439            a();
15440            b();
15441        //ˇ
15442        }
15443    "});
15444
15445    // If a selection span multiple lines, empty lines are not toggled.
15446    cx.set_state(indoc! {"
15447        fn a() {
15448            «a();
15449
15450            c();ˇ»
15451        }
15452    "});
15453
15454    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15455
15456    cx.assert_editor_state(indoc! {"
15457        fn a() {
15458        //    «a();
15459
15460        //    c();ˇ»
15461        }
15462    "});
15463
15464    // If a selection includes multiple comment prefixes, all lines are uncommented.
15465    cx.set_state(indoc! {"
15466        fn a() {
15467        //    «a();
15468        ///    b();
15469        //!    c();ˇ»
15470        }
15471    "});
15472
15473    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15474
15475    cx.assert_editor_state(indoc! {"
15476        fn a() {
15477            «a();
15478            b();
15479            c();ˇ»
15480        }
15481    "});
15482}
15483
15484#[gpui::test]
15485async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15486    init_test(cx, |_| {});
15487
15488    let language = Arc::new(Language::new(
15489        LanguageConfig {
15490            line_comments: vec!["// ".into()],
15491            ..Default::default()
15492        },
15493        Some(tree_sitter_rust::LANGUAGE.into()),
15494    ));
15495
15496    let mut cx = EditorTestContext::new(cx).await;
15497
15498    cx.language_registry().add(language.clone());
15499    cx.update_buffer(|buffer, cx| {
15500        buffer.set_language(Some(language), cx);
15501    });
15502
15503    let toggle_comments = &ToggleComments {
15504        advance_downwards: true,
15505        ignore_indent: false,
15506    };
15507
15508    // Single cursor on one line -> advance
15509    // Cursor moves horizontally 3 characters as well on non-blank line
15510    cx.set_state(indoc!(
15511        "fn a() {
15512             ˇdog();
15513             cat();
15514        }"
15515    ));
15516    cx.update_editor(|editor, window, cx| {
15517        editor.toggle_comments(toggle_comments, window, cx);
15518    });
15519    cx.assert_editor_state(indoc!(
15520        "fn a() {
15521             // dog();
15522             catˇ();
15523        }"
15524    ));
15525
15526    // Single selection on one line -> don't advance
15527    cx.set_state(indoc!(
15528        "fn a() {
15529             «dog()ˇ»;
15530             cat();
15531        }"
15532    ));
15533    cx.update_editor(|editor, window, cx| {
15534        editor.toggle_comments(toggle_comments, window, cx);
15535    });
15536    cx.assert_editor_state(indoc!(
15537        "fn a() {
15538             // «dog()ˇ»;
15539             cat();
15540        }"
15541    ));
15542
15543    // Multiple cursors on one line -> advance
15544    cx.set_state(indoc!(
15545        "fn a() {
15546             ˇdˇog();
15547             cat();
15548        }"
15549    ));
15550    cx.update_editor(|editor, window, cx| {
15551        editor.toggle_comments(toggle_comments, window, cx);
15552    });
15553    cx.assert_editor_state(indoc!(
15554        "fn a() {
15555             // dog();
15556             catˇ(ˇ);
15557        }"
15558    ));
15559
15560    // Multiple cursors on one line, with selection -> don't advance
15561    cx.set_state(indoc!(
15562        "fn a() {
15563             ˇdˇog«()ˇ»;
15564             cat();
15565        }"
15566    ));
15567    cx.update_editor(|editor, window, cx| {
15568        editor.toggle_comments(toggle_comments, window, cx);
15569    });
15570    cx.assert_editor_state(indoc!(
15571        "fn a() {
15572             // ˇdˇog«()ˇ»;
15573             cat();
15574        }"
15575    ));
15576
15577    // Single cursor on one line -> advance
15578    // Cursor moves to column 0 on blank line
15579    cx.set_state(indoc!(
15580        "fn a() {
15581             ˇdog();
15582
15583             cat();
15584        }"
15585    ));
15586    cx.update_editor(|editor, window, cx| {
15587        editor.toggle_comments(toggle_comments, window, cx);
15588    });
15589    cx.assert_editor_state(indoc!(
15590        "fn a() {
15591             // dog();
15592        ˇ
15593             cat();
15594        }"
15595    ));
15596
15597    // Single cursor on one line -> advance
15598    // Cursor starts and ends at column 0
15599    cx.set_state(indoc!(
15600        "fn a() {
15601         ˇ    dog();
15602             cat();
15603        }"
15604    ));
15605    cx.update_editor(|editor, window, cx| {
15606        editor.toggle_comments(toggle_comments, window, cx);
15607    });
15608    cx.assert_editor_state(indoc!(
15609        "fn a() {
15610             // dog();
15611         ˇ    cat();
15612        }"
15613    ));
15614}
15615
15616#[gpui::test]
15617async fn test_toggle_block_comment(cx: &mut TestAppContext) {
15618    init_test(cx, |_| {});
15619
15620    let mut cx = EditorTestContext::new(cx).await;
15621
15622    let html_language = Arc::new(
15623        Language::new(
15624            LanguageConfig {
15625                name: "HTML".into(),
15626                block_comment: Some(BlockCommentConfig {
15627                    start: "<!-- ".into(),
15628                    prefix: "".into(),
15629                    end: " -->".into(),
15630                    tab_size: 0,
15631                }),
15632                ..Default::default()
15633            },
15634            Some(tree_sitter_html::LANGUAGE.into()),
15635        )
15636        .with_injection_query(
15637            r#"
15638            (script_element
15639                (raw_text) @injection.content
15640                (#set! injection.language "javascript"))
15641            "#,
15642        )
15643        .unwrap(),
15644    );
15645
15646    let javascript_language = Arc::new(Language::new(
15647        LanguageConfig {
15648            name: "JavaScript".into(),
15649            line_comments: vec!["// ".into()],
15650            ..Default::default()
15651        },
15652        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15653    ));
15654
15655    cx.language_registry().add(html_language.clone());
15656    cx.language_registry().add(javascript_language);
15657    cx.update_buffer(|buffer, cx| {
15658        buffer.set_language(Some(html_language), cx);
15659    });
15660
15661    // Toggle comments for empty selections
15662    cx.set_state(
15663        &r#"
15664            <p>A</p>ˇ
15665            <p>B</p>ˇ
15666            <p>C</p>ˇ
15667        "#
15668        .unindent(),
15669    );
15670    cx.update_editor(|editor, window, cx| {
15671        editor.toggle_comments(&ToggleComments::default(), window, cx)
15672    });
15673    cx.assert_editor_state(
15674        &r#"
15675            <!-- <p>A</p>ˇ -->
15676            <!-- <p>B</p>ˇ -->
15677            <!-- <p>C</p>ˇ -->
15678        "#
15679        .unindent(),
15680    );
15681    cx.update_editor(|editor, window, cx| {
15682        editor.toggle_comments(&ToggleComments::default(), window, cx)
15683    });
15684    cx.assert_editor_state(
15685        &r#"
15686            <p>A</p>ˇ
15687            <p>B</p>ˇ
15688            <p>C</p>ˇ
15689        "#
15690        .unindent(),
15691    );
15692
15693    // Toggle comments for mixture of empty and non-empty selections, where
15694    // multiple selections occupy a given line.
15695    cx.set_state(
15696        &r#"
15697            <p>A«</p>
15698            <p>ˇ»B</p>ˇ
15699            <p>C«</p>
15700            <p>ˇ»D</p>ˇ
15701        "#
15702        .unindent(),
15703    );
15704
15705    cx.update_editor(|editor, window, cx| {
15706        editor.toggle_comments(&ToggleComments::default(), window, cx)
15707    });
15708    cx.assert_editor_state(
15709        &r#"
15710            <!-- <p>A«</p>
15711            <p>ˇ»B</p>ˇ -->
15712            <!-- <p>C«</p>
15713            <p>ˇ»D</p>ˇ -->
15714        "#
15715        .unindent(),
15716    );
15717    cx.update_editor(|editor, window, cx| {
15718        editor.toggle_comments(&ToggleComments::default(), window, cx)
15719    });
15720    cx.assert_editor_state(
15721        &r#"
15722            <p>A«</p>
15723            <p>ˇ»B</p>ˇ
15724            <p>C«</p>
15725            <p>ˇ»D</p>ˇ
15726        "#
15727        .unindent(),
15728    );
15729
15730    // Toggle comments when different languages are active for different
15731    // selections.
15732    cx.set_state(
15733        &r#"
15734            ˇ<script>
15735                ˇvar x = new Y();
15736            ˇ</script>
15737        "#
15738        .unindent(),
15739    );
15740    cx.executor().run_until_parked();
15741    cx.update_editor(|editor, window, cx| {
15742        editor.toggle_comments(&ToggleComments::default(), window, cx)
15743    });
15744    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
15745    // Uncommenting and commenting from this position brings in even more wrong artifacts.
15746    cx.assert_editor_state(
15747        &r#"
15748            <!-- ˇ<script> -->
15749                // ˇvar x = new Y();
15750            <!-- ˇ</script> -->
15751        "#
15752        .unindent(),
15753    );
15754}
15755
15756#[gpui::test]
15757fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
15758    init_test(cx, |_| {});
15759
15760    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15761    let multibuffer = cx.new(|cx| {
15762        let mut multibuffer = MultiBuffer::new(ReadWrite);
15763        multibuffer.push_excerpts(
15764            buffer.clone(),
15765            [
15766                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
15767                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
15768            ],
15769            cx,
15770        );
15771        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
15772        multibuffer
15773    });
15774
15775    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15776    editor.update_in(cx, |editor, window, cx| {
15777        assert_eq!(editor.text(cx), "aaaa\nbbbb");
15778        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15779            s.select_ranges([
15780                Point::new(0, 0)..Point::new(0, 0),
15781                Point::new(1, 0)..Point::new(1, 0),
15782            ])
15783        });
15784
15785        editor.handle_input("X", window, cx);
15786        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
15787        assert_eq!(
15788            editor.selections.ranges(cx),
15789            [
15790                Point::new(0, 1)..Point::new(0, 1),
15791                Point::new(1, 1)..Point::new(1, 1),
15792            ]
15793        );
15794
15795        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
15796        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15797            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
15798        });
15799        editor.backspace(&Default::default(), window, cx);
15800        assert_eq!(editor.text(cx), "Xa\nbbb");
15801        assert_eq!(
15802            editor.selections.ranges(cx),
15803            [Point::new(1, 0)..Point::new(1, 0)]
15804        );
15805
15806        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15807            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
15808        });
15809        editor.backspace(&Default::default(), window, cx);
15810        assert_eq!(editor.text(cx), "X\nbb");
15811        assert_eq!(
15812            editor.selections.ranges(cx),
15813            [Point::new(0, 1)..Point::new(0, 1)]
15814        );
15815    });
15816}
15817
15818#[gpui::test]
15819fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
15820    init_test(cx, |_| {});
15821
15822    let markers = vec![('[', ']').into(), ('(', ')').into()];
15823    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
15824        indoc! {"
15825            [aaaa
15826            (bbbb]
15827            cccc)",
15828        },
15829        markers.clone(),
15830    );
15831    let excerpt_ranges = markers.into_iter().map(|marker| {
15832        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
15833        ExcerptRange::new(context)
15834    });
15835    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
15836    let multibuffer = cx.new(|cx| {
15837        let mut multibuffer = MultiBuffer::new(ReadWrite);
15838        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
15839        multibuffer
15840    });
15841
15842    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15843    editor.update_in(cx, |editor, window, cx| {
15844        let (expected_text, selection_ranges) = marked_text_ranges(
15845            indoc! {"
15846                aaaa
15847                bˇbbb
15848                bˇbbˇb
15849                cccc"
15850            },
15851            true,
15852        );
15853        assert_eq!(editor.text(cx), expected_text);
15854        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15855            s.select_ranges(selection_ranges)
15856        });
15857
15858        editor.handle_input("X", window, cx);
15859
15860        let (expected_text, expected_selections) = marked_text_ranges(
15861            indoc! {"
15862                aaaa
15863                bXˇbbXb
15864                bXˇbbXˇb
15865                cccc"
15866            },
15867            false,
15868        );
15869        assert_eq!(editor.text(cx), expected_text);
15870        assert_eq!(editor.selections.ranges(cx), expected_selections);
15871
15872        editor.newline(&Newline, window, cx);
15873        let (expected_text, expected_selections) = marked_text_ranges(
15874            indoc! {"
15875                aaaa
15876                bX
15877                ˇbbX
15878                b
15879                bX
15880                ˇbbX
15881                ˇb
15882                cccc"
15883            },
15884            false,
15885        );
15886        assert_eq!(editor.text(cx), expected_text);
15887        assert_eq!(editor.selections.ranges(cx), expected_selections);
15888    });
15889}
15890
15891#[gpui::test]
15892fn test_refresh_selections(cx: &mut TestAppContext) {
15893    init_test(cx, |_| {});
15894
15895    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15896    let mut excerpt1_id = None;
15897    let multibuffer = cx.new(|cx| {
15898        let mut multibuffer = MultiBuffer::new(ReadWrite);
15899        excerpt1_id = multibuffer
15900            .push_excerpts(
15901                buffer.clone(),
15902                [
15903                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15904                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15905                ],
15906                cx,
15907            )
15908            .into_iter()
15909            .next();
15910        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15911        multibuffer
15912    });
15913
15914    let editor = cx.add_window(|window, cx| {
15915        let mut editor = build_editor(multibuffer.clone(), window, cx);
15916        let snapshot = editor.snapshot(window, cx);
15917        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15918            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
15919        });
15920        editor.begin_selection(
15921            Point::new(2, 1).to_display_point(&snapshot),
15922            true,
15923            1,
15924            window,
15925            cx,
15926        );
15927        assert_eq!(
15928            editor.selections.ranges(cx),
15929            [
15930                Point::new(1, 3)..Point::new(1, 3),
15931                Point::new(2, 1)..Point::new(2, 1),
15932            ]
15933        );
15934        editor
15935    });
15936
15937    // Refreshing selections is a no-op when excerpts haven't changed.
15938    _ = editor.update(cx, |editor, window, cx| {
15939        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15940        assert_eq!(
15941            editor.selections.ranges(cx),
15942            [
15943                Point::new(1, 3)..Point::new(1, 3),
15944                Point::new(2, 1)..Point::new(2, 1),
15945            ]
15946        );
15947    });
15948
15949    multibuffer.update(cx, |multibuffer, cx| {
15950        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
15951    });
15952    _ = editor.update(cx, |editor, window, cx| {
15953        // Removing an excerpt causes the first selection to become degenerate.
15954        assert_eq!(
15955            editor.selections.ranges(cx),
15956            [
15957                Point::new(0, 0)..Point::new(0, 0),
15958                Point::new(0, 1)..Point::new(0, 1)
15959            ]
15960        );
15961
15962        // Refreshing selections will relocate the first selection to the original buffer
15963        // location.
15964        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15965        assert_eq!(
15966            editor.selections.ranges(cx),
15967            [
15968                Point::new(0, 1)..Point::new(0, 1),
15969                Point::new(0, 3)..Point::new(0, 3)
15970            ]
15971        );
15972        assert!(editor.selections.pending_anchor().is_some());
15973    });
15974}
15975
15976#[gpui::test]
15977fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
15978    init_test(cx, |_| {});
15979
15980    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15981    let mut excerpt1_id = None;
15982    let multibuffer = cx.new(|cx| {
15983        let mut multibuffer = MultiBuffer::new(ReadWrite);
15984        excerpt1_id = multibuffer
15985            .push_excerpts(
15986                buffer.clone(),
15987                [
15988                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15989                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15990                ],
15991                cx,
15992            )
15993            .into_iter()
15994            .next();
15995        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15996        multibuffer
15997    });
15998
15999    let editor = cx.add_window(|window, cx| {
16000        let mut editor = build_editor(multibuffer.clone(), window, cx);
16001        let snapshot = editor.snapshot(window, cx);
16002        editor.begin_selection(
16003            Point::new(1, 3).to_display_point(&snapshot),
16004            false,
16005            1,
16006            window,
16007            cx,
16008        );
16009        assert_eq!(
16010            editor.selections.ranges(cx),
16011            [Point::new(1, 3)..Point::new(1, 3)]
16012        );
16013        editor
16014    });
16015
16016    multibuffer.update(cx, |multibuffer, cx| {
16017        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16018    });
16019    _ = editor.update(cx, |editor, window, cx| {
16020        assert_eq!(
16021            editor.selections.ranges(cx),
16022            [Point::new(0, 0)..Point::new(0, 0)]
16023        );
16024
16025        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
16026        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16027        assert_eq!(
16028            editor.selections.ranges(cx),
16029            [Point::new(0, 3)..Point::new(0, 3)]
16030        );
16031        assert!(editor.selections.pending_anchor().is_some());
16032    });
16033}
16034
16035#[gpui::test]
16036async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
16037    init_test(cx, |_| {});
16038
16039    let language = Arc::new(
16040        Language::new(
16041            LanguageConfig {
16042                brackets: BracketPairConfig {
16043                    pairs: vec![
16044                        BracketPair {
16045                            start: "{".to_string(),
16046                            end: "}".to_string(),
16047                            close: true,
16048                            surround: true,
16049                            newline: true,
16050                        },
16051                        BracketPair {
16052                            start: "/* ".to_string(),
16053                            end: " */".to_string(),
16054                            close: true,
16055                            surround: true,
16056                            newline: true,
16057                        },
16058                    ],
16059                    ..Default::default()
16060                },
16061                ..Default::default()
16062            },
16063            Some(tree_sitter_rust::LANGUAGE.into()),
16064        )
16065        .with_indents_query("")
16066        .unwrap(),
16067    );
16068
16069    let text = concat!(
16070        "{   }\n",     //
16071        "  x\n",       //
16072        "  /*   */\n", //
16073        "x\n",         //
16074        "{{} }\n",     //
16075    );
16076
16077    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16078    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16079    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16080    editor
16081        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16082        .await;
16083
16084    editor.update_in(cx, |editor, window, cx| {
16085        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16086            s.select_display_ranges([
16087                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16088                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16089                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16090            ])
16091        });
16092        editor.newline(&Newline, window, cx);
16093
16094        assert_eq!(
16095            editor.buffer().read(cx).read(cx).text(),
16096            concat!(
16097                "{ \n",    // Suppress rustfmt
16098                "\n",      //
16099                "}\n",     //
16100                "  x\n",   //
16101                "  /* \n", //
16102                "  \n",    //
16103                "  */\n",  //
16104                "x\n",     //
16105                "{{} \n",  //
16106                "}\n",     //
16107            )
16108        );
16109    });
16110}
16111
16112#[gpui::test]
16113fn test_highlighted_ranges(cx: &mut TestAppContext) {
16114    init_test(cx, |_| {});
16115
16116    let editor = cx.add_window(|window, cx| {
16117        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16118        build_editor(buffer, window, cx)
16119    });
16120
16121    _ = editor.update(cx, |editor, window, cx| {
16122        struct Type1;
16123        struct Type2;
16124
16125        let buffer = editor.buffer.read(cx).snapshot(cx);
16126
16127        let anchor_range =
16128            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16129
16130        editor.highlight_background::<Type1>(
16131            &[
16132                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16133                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16134                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16135                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16136            ],
16137            |_| Hsla::red(),
16138            cx,
16139        );
16140        editor.highlight_background::<Type2>(
16141            &[
16142                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16143                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16144                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16145                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16146            ],
16147            |_| Hsla::green(),
16148            cx,
16149        );
16150
16151        let snapshot = editor.snapshot(window, cx);
16152        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16153            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16154            &snapshot,
16155            cx.theme(),
16156        );
16157        assert_eq!(
16158            highlighted_ranges,
16159            &[
16160                (
16161                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16162                    Hsla::green(),
16163                ),
16164                (
16165                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16166                    Hsla::red(),
16167                ),
16168                (
16169                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16170                    Hsla::green(),
16171                ),
16172                (
16173                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16174                    Hsla::red(),
16175                ),
16176            ]
16177        );
16178        assert_eq!(
16179            editor.sorted_background_highlights_in_range(
16180                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16181                &snapshot,
16182                cx.theme(),
16183            ),
16184            &[(
16185                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16186                Hsla::red(),
16187            )]
16188        );
16189    });
16190}
16191
16192#[gpui::test]
16193async fn test_following(cx: &mut TestAppContext) {
16194    init_test(cx, |_| {});
16195
16196    let fs = FakeFs::new(cx.executor());
16197    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16198
16199    let buffer = project.update(cx, |project, cx| {
16200        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16201        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16202    });
16203    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16204    let follower = cx.update(|cx| {
16205        cx.open_window(
16206            WindowOptions {
16207                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16208                    gpui::Point::new(px(0.), px(0.)),
16209                    gpui::Point::new(px(10.), px(80.)),
16210                ))),
16211                ..Default::default()
16212            },
16213            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16214        )
16215        .unwrap()
16216    });
16217
16218    let is_still_following = Rc::new(RefCell::new(true));
16219    let follower_edit_event_count = Rc::new(RefCell::new(0));
16220    let pending_update = Rc::new(RefCell::new(None));
16221    let leader_entity = leader.root(cx).unwrap();
16222    let follower_entity = follower.root(cx).unwrap();
16223    _ = follower.update(cx, {
16224        let update = pending_update.clone();
16225        let is_still_following = is_still_following.clone();
16226        let follower_edit_event_count = follower_edit_event_count.clone();
16227        |_, window, cx| {
16228            cx.subscribe_in(
16229                &leader_entity,
16230                window,
16231                move |_, leader, event, window, cx| {
16232                    leader.read(cx).add_event_to_update_proto(
16233                        event,
16234                        &mut update.borrow_mut(),
16235                        window,
16236                        cx,
16237                    );
16238                },
16239            )
16240            .detach();
16241
16242            cx.subscribe_in(
16243                &follower_entity,
16244                window,
16245                move |_, _, event: &EditorEvent, _window, _cx| {
16246                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16247                        *is_still_following.borrow_mut() = false;
16248                    }
16249
16250                    if let EditorEvent::BufferEdited = event {
16251                        *follower_edit_event_count.borrow_mut() += 1;
16252                    }
16253                },
16254            )
16255            .detach();
16256        }
16257    });
16258
16259    // Update the selections only
16260    _ = leader.update(cx, |leader, window, cx| {
16261        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16262            s.select_ranges([1..1])
16263        });
16264    });
16265    follower
16266        .update(cx, |follower, window, cx| {
16267            follower.apply_update_proto(
16268                &project,
16269                pending_update.borrow_mut().take().unwrap(),
16270                window,
16271                cx,
16272            )
16273        })
16274        .unwrap()
16275        .await
16276        .unwrap();
16277    _ = follower.update(cx, |follower, _, cx| {
16278        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
16279    });
16280    assert!(*is_still_following.borrow());
16281    assert_eq!(*follower_edit_event_count.borrow(), 0);
16282
16283    // Update the scroll position only
16284    _ = leader.update(cx, |leader, window, cx| {
16285        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16286    });
16287    follower
16288        .update(cx, |follower, window, cx| {
16289            follower.apply_update_proto(
16290                &project,
16291                pending_update.borrow_mut().take().unwrap(),
16292                window,
16293                cx,
16294            )
16295        })
16296        .unwrap()
16297        .await
16298        .unwrap();
16299    assert_eq!(
16300        follower
16301            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16302            .unwrap(),
16303        gpui::Point::new(1.5, 3.5)
16304    );
16305    assert!(*is_still_following.borrow());
16306    assert_eq!(*follower_edit_event_count.borrow(), 0);
16307
16308    // Update the selections and scroll position. The follower's scroll position is updated
16309    // via autoscroll, not via the leader's exact scroll position.
16310    _ = leader.update(cx, |leader, window, cx| {
16311        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16312            s.select_ranges([0..0])
16313        });
16314        leader.request_autoscroll(Autoscroll::newest(), cx);
16315        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16316    });
16317    follower
16318        .update(cx, |follower, window, cx| {
16319            follower.apply_update_proto(
16320                &project,
16321                pending_update.borrow_mut().take().unwrap(),
16322                window,
16323                cx,
16324            )
16325        })
16326        .unwrap()
16327        .await
16328        .unwrap();
16329    _ = follower.update(cx, |follower, _, cx| {
16330        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16331        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
16332    });
16333    assert!(*is_still_following.borrow());
16334
16335    // Creating a pending selection that precedes another selection
16336    _ = leader.update(cx, |leader, window, cx| {
16337        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16338            s.select_ranges([1..1])
16339        });
16340        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16341    });
16342    follower
16343        .update(cx, |follower, window, cx| {
16344            follower.apply_update_proto(
16345                &project,
16346                pending_update.borrow_mut().take().unwrap(),
16347                window,
16348                cx,
16349            )
16350        })
16351        .unwrap()
16352        .await
16353        .unwrap();
16354    _ = follower.update(cx, |follower, _, cx| {
16355        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
16356    });
16357    assert!(*is_still_following.borrow());
16358
16359    // Extend the pending selection so that it surrounds another selection
16360    _ = leader.update(cx, |leader, window, cx| {
16361        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16362    });
16363    follower
16364        .update(cx, |follower, window, cx| {
16365            follower.apply_update_proto(
16366                &project,
16367                pending_update.borrow_mut().take().unwrap(),
16368                window,
16369                cx,
16370            )
16371        })
16372        .unwrap()
16373        .await
16374        .unwrap();
16375    _ = follower.update(cx, |follower, _, cx| {
16376        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
16377    });
16378
16379    // Scrolling locally breaks the follow
16380    _ = follower.update(cx, |follower, window, cx| {
16381        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16382        follower.set_scroll_anchor(
16383            ScrollAnchor {
16384                anchor: top_anchor,
16385                offset: gpui::Point::new(0.0, 0.5),
16386            },
16387            window,
16388            cx,
16389        );
16390    });
16391    assert!(!(*is_still_following.borrow()));
16392}
16393
16394#[gpui::test]
16395async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16396    init_test(cx, |_| {});
16397
16398    let fs = FakeFs::new(cx.executor());
16399    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16400    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16401    let pane = workspace
16402        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16403        .unwrap();
16404
16405    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16406
16407    let leader = pane.update_in(cx, |_, window, cx| {
16408        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16409        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16410    });
16411
16412    // Start following the editor when it has no excerpts.
16413    let mut state_message =
16414        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16415    let workspace_entity = workspace.root(cx).unwrap();
16416    let follower_1 = cx
16417        .update_window(*workspace.deref(), |_, window, cx| {
16418            Editor::from_state_proto(
16419                workspace_entity,
16420                ViewId {
16421                    creator: CollaboratorId::PeerId(PeerId::default()),
16422                    id: 0,
16423                },
16424                &mut state_message,
16425                window,
16426                cx,
16427            )
16428        })
16429        .unwrap()
16430        .unwrap()
16431        .await
16432        .unwrap();
16433
16434    let update_message = Rc::new(RefCell::new(None));
16435    follower_1.update_in(cx, {
16436        let update = update_message.clone();
16437        |_, window, cx| {
16438            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16439                leader.read(cx).add_event_to_update_proto(
16440                    event,
16441                    &mut update.borrow_mut(),
16442                    window,
16443                    cx,
16444                );
16445            })
16446            .detach();
16447        }
16448    });
16449
16450    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16451        (
16452            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16453            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16454        )
16455    });
16456
16457    // Insert some excerpts.
16458    leader.update(cx, |leader, cx| {
16459        leader.buffer.update(cx, |multibuffer, cx| {
16460            multibuffer.set_excerpts_for_path(
16461                PathKey::namespaced(1, "b.txt".into()),
16462                buffer_1.clone(),
16463                vec![
16464                    Point::row_range(0..3),
16465                    Point::row_range(1..6),
16466                    Point::row_range(12..15),
16467                ],
16468                0,
16469                cx,
16470            );
16471            multibuffer.set_excerpts_for_path(
16472                PathKey::namespaced(1, "a.txt".into()),
16473                buffer_2.clone(),
16474                vec![Point::row_range(0..6), Point::row_range(8..12)],
16475                0,
16476                cx,
16477            );
16478        });
16479    });
16480
16481    // Apply the update of adding the excerpts.
16482    follower_1
16483        .update_in(cx, |follower, window, cx| {
16484            follower.apply_update_proto(
16485                &project,
16486                update_message.borrow().clone().unwrap(),
16487                window,
16488                cx,
16489            )
16490        })
16491        .await
16492        .unwrap();
16493    assert_eq!(
16494        follower_1.update(cx, |editor, cx| editor.text(cx)),
16495        leader.update(cx, |editor, cx| editor.text(cx))
16496    );
16497    update_message.borrow_mut().take();
16498
16499    // Start following separately after it already has excerpts.
16500    let mut state_message =
16501        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16502    let workspace_entity = workspace.root(cx).unwrap();
16503    let follower_2 = cx
16504        .update_window(*workspace.deref(), |_, window, cx| {
16505            Editor::from_state_proto(
16506                workspace_entity,
16507                ViewId {
16508                    creator: CollaboratorId::PeerId(PeerId::default()),
16509                    id: 0,
16510                },
16511                &mut state_message,
16512                window,
16513                cx,
16514            )
16515        })
16516        .unwrap()
16517        .unwrap()
16518        .await
16519        .unwrap();
16520    assert_eq!(
16521        follower_2.update(cx, |editor, cx| editor.text(cx)),
16522        leader.update(cx, |editor, cx| editor.text(cx))
16523    );
16524
16525    // Remove some excerpts.
16526    leader.update(cx, |leader, cx| {
16527        leader.buffer.update(cx, |multibuffer, cx| {
16528            let excerpt_ids = multibuffer.excerpt_ids();
16529            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16530            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16531        });
16532    });
16533
16534    // Apply the update of removing the excerpts.
16535    follower_1
16536        .update_in(cx, |follower, window, cx| {
16537            follower.apply_update_proto(
16538                &project,
16539                update_message.borrow().clone().unwrap(),
16540                window,
16541                cx,
16542            )
16543        })
16544        .await
16545        .unwrap();
16546    follower_2
16547        .update_in(cx, |follower, window, cx| {
16548            follower.apply_update_proto(
16549                &project,
16550                update_message.borrow().clone().unwrap(),
16551                window,
16552                cx,
16553            )
16554        })
16555        .await
16556        .unwrap();
16557    update_message.borrow_mut().take();
16558    assert_eq!(
16559        follower_1.update(cx, |editor, cx| editor.text(cx)),
16560        leader.update(cx, |editor, cx| editor.text(cx))
16561    );
16562}
16563
16564#[gpui::test]
16565async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16566    init_test(cx, |_| {});
16567
16568    let mut cx = EditorTestContext::new(cx).await;
16569    let lsp_store =
16570        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
16571
16572    cx.set_state(indoc! {"
16573        ˇfn func(abc def: i32) -> u32 {
16574        }
16575    "});
16576
16577    cx.update(|_, cx| {
16578        lsp_store.update(cx, |lsp_store, cx| {
16579            lsp_store
16580                .update_diagnostics(
16581                    LanguageServerId(0),
16582                    lsp::PublishDiagnosticsParams {
16583                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
16584                        version: None,
16585                        diagnostics: vec![
16586                            lsp::Diagnostic {
16587                                range: lsp::Range::new(
16588                                    lsp::Position::new(0, 11),
16589                                    lsp::Position::new(0, 12),
16590                                ),
16591                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16592                                ..Default::default()
16593                            },
16594                            lsp::Diagnostic {
16595                                range: lsp::Range::new(
16596                                    lsp::Position::new(0, 12),
16597                                    lsp::Position::new(0, 15),
16598                                ),
16599                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16600                                ..Default::default()
16601                            },
16602                            lsp::Diagnostic {
16603                                range: lsp::Range::new(
16604                                    lsp::Position::new(0, 25),
16605                                    lsp::Position::new(0, 28),
16606                                ),
16607                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16608                                ..Default::default()
16609                            },
16610                        ],
16611                    },
16612                    None,
16613                    DiagnosticSourceKind::Pushed,
16614                    &[],
16615                    cx,
16616                )
16617                .unwrap()
16618        });
16619    });
16620
16621    executor.run_until_parked();
16622
16623    cx.update_editor(|editor, window, cx| {
16624        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16625    });
16626
16627    cx.assert_editor_state(indoc! {"
16628        fn func(abc def: i32) -> ˇu32 {
16629        }
16630    "});
16631
16632    cx.update_editor(|editor, window, cx| {
16633        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16634    });
16635
16636    cx.assert_editor_state(indoc! {"
16637        fn func(abc ˇdef: i32) -> u32 {
16638        }
16639    "});
16640
16641    cx.update_editor(|editor, window, cx| {
16642        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16643    });
16644
16645    cx.assert_editor_state(indoc! {"
16646        fn func(abcˇ def: i32) -> u32 {
16647        }
16648    "});
16649
16650    cx.update_editor(|editor, window, cx| {
16651        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16652    });
16653
16654    cx.assert_editor_state(indoc! {"
16655        fn func(abc def: i32) -> ˇu32 {
16656        }
16657    "});
16658}
16659
16660#[gpui::test]
16661async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16662    init_test(cx, |_| {});
16663
16664    let mut cx = EditorTestContext::new(cx).await;
16665
16666    let diff_base = r#"
16667        use some::mod;
16668
16669        const A: u32 = 42;
16670
16671        fn main() {
16672            println!("hello");
16673
16674            println!("world");
16675        }
16676        "#
16677    .unindent();
16678
16679    // Edits are modified, removed, modified, added
16680    cx.set_state(
16681        &r#"
16682        use some::modified;
16683
16684        ˇ
16685        fn main() {
16686            println!("hello there");
16687
16688            println!("around the");
16689            println!("world");
16690        }
16691        "#
16692        .unindent(),
16693    );
16694
16695    cx.set_head_text(&diff_base);
16696    executor.run_until_parked();
16697
16698    cx.update_editor(|editor, window, cx| {
16699        //Wrap around the bottom of the buffer
16700        for _ in 0..3 {
16701            editor.go_to_next_hunk(&GoToHunk, window, cx);
16702        }
16703    });
16704
16705    cx.assert_editor_state(
16706        &r#"
16707        ˇuse some::modified;
16708
16709
16710        fn main() {
16711            println!("hello there");
16712
16713            println!("around the");
16714            println!("world");
16715        }
16716        "#
16717        .unindent(),
16718    );
16719
16720    cx.update_editor(|editor, window, cx| {
16721        //Wrap around the top of the buffer
16722        for _ in 0..2 {
16723            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16724        }
16725    });
16726
16727    cx.assert_editor_state(
16728        &r#"
16729        use some::modified;
16730
16731
16732        fn main() {
16733        ˇ    println!("hello there");
16734
16735            println!("around the");
16736            println!("world");
16737        }
16738        "#
16739        .unindent(),
16740    );
16741
16742    cx.update_editor(|editor, window, cx| {
16743        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16744    });
16745
16746    cx.assert_editor_state(
16747        &r#"
16748        use some::modified;
16749
16750        ˇ
16751        fn main() {
16752            println!("hello there");
16753
16754            println!("around the");
16755            println!("world");
16756        }
16757        "#
16758        .unindent(),
16759    );
16760
16761    cx.update_editor(|editor, window, cx| {
16762        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16763    });
16764
16765    cx.assert_editor_state(
16766        &r#"
16767        ˇuse some::modified;
16768
16769
16770        fn main() {
16771            println!("hello there");
16772
16773            println!("around the");
16774            println!("world");
16775        }
16776        "#
16777        .unindent(),
16778    );
16779
16780    cx.update_editor(|editor, window, cx| {
16781        for _ in 0..2 {
16782            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16783        }
16784    });
16785
16786    cx.assert_editor_state(
16787        &r#"
16788        use some::modified;
16789
16790
16791        fn main() {
16792        ˇ    println!("hello there");
16793
16794            println!("around the");
16795            println!("world");
16796        }
16797        "#
16798        .unindent(),
16799    );
16800
16801    cx.update_editor(|editor, window, cx| {
16802        editor.fold(&Fold, window, cx);
16803    });
16804
16805    cx.update_editor(|editor, window, cx| {
16806        editor.go_to_next_hunk(&GoToHunk, window, cx);
16807    });
16808
16809    cx.assert_editor_state(
16810        &r#"
16811        ˇuse some::modified;
16812
16813
16814        fn main() {
16815            println!("hello there");
16816
16817            println!("around the");
16818            println!("world");
16819        }
16820        "#
16821        .unindent(),
16822    );
16823}
16824
16825#[test]
16826fn test_split_words() {
16827    fn split(text: &str) -> Vec<&str> {
16828        split_words(text).collect()
16829    }
16830
16831    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
16832    assert_eq!(split("hello_world"), &["hello_", "world"]);
16833    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
16834    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
16835    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
16836    assert_eq!(split("helloworld"), &["helloworld"]);
16837
16838    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
16839}
16840
16841#[gpui::test]
16842async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
16843    init_test(cx, |_| {});
16844
16845    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
16846    let mut assert = |before, after| {
16847        let _state_context = cx.set_state(before);
16848        cx.run_until_parked();
16849        cx.update_editor(|editor, window, cx| {
16850            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
16851        });
16852        cx.run_until_parked();
16853        cx.assert_editor_state(after);
16854    };
16855
16856    // Outside bracket jumps to outside of matching bracket
16857    assert("console.logˇ(var);", "console.log(var)ˇ;");
16858    assert("console.log(var)ˇ;", "console.logˇ(var);");
16859
16860    // Inside bracket jumps to inside of matching bracket
16861    assert("console.log(ˇvar);", "console.log(varˇ);");
16862    assert("console.log(varˇ);", "console.log(ˇvar);");
16863
16864    // When outside a bracket and inside, favor jumping to the inside bracket
16865    assert(
16866        "console.log('foo', [1, 2, 3]ˇ);",
16867        "console.log(ˇ'foo', [1, 2, 3]);",
16868    );
16869    assert(
16870        "console.log(ˇ'foo', [1, 2, 3]);",
16871        "console.log('foo', [1, 2, 3]ˇ);",
16872    );
16873
16874    // Bias forward if two options are equally likely
16875    assert(
16876        "let result = curried_fun()ˇ();",
16877        "let result = curried_fun()()ˇ;",
16878    );
16879
16880    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
16881    assert(
16882        indoc! {"
16883            function test() {
16884                console.log('test')ˇ
16885            }"},
16886        indoc! {"
16887            function test() {
16888                console.logˇ('test')
16889            }"},
16890    );
16891}
16892
16893#[gpui::test]
16894async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
16895    init_test(cx, |_| {});
16896
16897    let fs = FakeFs::new(cx.executor());
16898    fs.insert_tree(
16899        path!("/a"),
16900        json!({
16901            "main.rs": "fn main() { let a = 5; }",
16902            "other.rs": "// Test file",
16903        }),
16904    )
16905    .await;
16906    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16907
16908    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16909    language_registry.add(Arc::new(Language::new(
16910        LanguageConfig {
16911            name: "Rust".into(),
16912            matcher: LanguageMatcher {
16913                path_suffixes: vec!["rs".to_string()],
16914                ..Default::default()
16915            },
16916            brackets: BracketPairConfig {
16917                pairs: vec![BracketPair {
16918                    start: "{".to_string(),
16919                    end: "}".to_string(),
16920                    close: true,
16921                    surround: true,
16922                    newline: true,
16923                }],
16924                disabled_scopes_by_bracket_ix: Vec::new(),
16925            },
16926            ..Default::default()
16927        },
16928        Some(tree_sitter_rust::LANGUAGE.into()),
16929    )));
16930    let mut fake_servers = language_registry.register_fake_lsp(
16931        "Rust",
16932        FakeLspAdapter {
16933            capabilities: lsp::ServerCapabilities {
16934                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
16935                    first_trigger_character: "{".to_string(),
16936                    more_trigger_character: None,
16937                }),
16938                ..Default::default()
16939            },
16940            ..Default::default()
16941        },
16942    );
16943
16944    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16945
16946    let cx = &mut VisualTestContext::from_window(*workspace, cx);
16947
16948    let worktree_id = workspace
16949        .update(cx, |workspace, _, cx| {
16950            workspace.project().update(cx, |project, cx| {
16951                project.worktrees(cx).next().unwrap().read(cx).id()
16952            })
16953        })
16954        .unwrap();
16955
16956    let buffer = project
16957        .update(cx, |project, cx| {
16958            project.open_local_buffer(path!("/a/main.rs"), cx)
16959        })
16960        .await
16961        .unwrap();
16962    let editor_handle = workspace
16963        .update(cx, |workspace, window, cx| {
16964            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
16965        })
16966        .unwrap()
16967        .await
16968        .unwrap()
16969        .downcast::<Editor>()
16970        .unwrap();
16971
16972    cx.executor().start_waiting();
16973    let fake_server = fake_servers.next().await.unwrap();
16974
16975    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
16976        |params, _| async move {
16977            assert_eq!(
16978                params.text_document_position.text_document.uri,
16979                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
16980            );
16981            assert_eq!(
16982                params.text_document_position.position,
16983                lsp::Position::new(0, 21),
16984            );
16985
16986            Ok(Some(vec![lsp::TextEdit {
16987                new_text: "]".to_string(),
16988                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16989            }]))
16990        },
16991    );
16992
16993    editor_handle.update_in(cx, |editor, window, cx| {
16994        window.focus(&editor.focus_handle(cx));
16995        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16996            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
16997        });
16998        editor.handle_input("{", window, cx);
16999    });
17000
17001    cx.executor().run_until_parked();
17002
17003    buffer.update(cx, |buffer, _| {
17004        assert_eq!(
17005            buffer.text(),
17006            "fn main() { let a = {5}; }",
17007            "No extra braces from on type formatting should appear in the buffer"
17008        )
17009    });
17010}
17011
17012#[gpui::test(iterations = 20, seeds(31))]
17013async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
17014    init_test(cx, |_| {});
17015
17016    let mut cx = EditorLspTestContext::new_rust(
17017        lsp::ServerCapabilities {
17018            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17019                first_trigger_character: ".".to_string(),
17020                more_trigger_character: None,
17021            }),
17022            ..Default::default()
17023        },
17024        cx,
17025    )
17026    .await;
17027
17028    cx.update_buffer(|buffer, _| {
17029        // This causes autoindent to be async.
17030        buffer.set_sync_parse_timeout(Duration::ZERO)
17031    });
17032
17033    cx.set_state("fn c() {\n    d()ˇ\n}\n");
17034    cx.simulate_keystroke("\n");
17035    cx.run_until_parked();
17036
17037    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
17038    let mut request =
17039        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
17040            let buffer_cloned = buffer_cloned.clone();
17041            async move {
17042                buffer_cloned.update(&mut cx, |buffer, _| {
17043                    assert_eq!(
17044                        buffer.text(),
17045                        "fn c() {\n    d()\n        .\n}\n",
17046                        "OnTypeFormatting should triggered after autoindent applied"
17047                    )
17048                })?;
17049
17050                Ok(Some(vec![]))
17051            }
17052        });
17053
17054    cx.simulate_keystroke(".");
17055    cx.run_until_parked();
17056
17057    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
17058    assert!(request.next().await.is_some());
17059    request.close();
17060    assert!(request.next().await.is_none());
17061}
17062
17063#[gpui::test]
17064async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17065    init_test(cx, |_| {});
17066
17067    let fs = FakeFs::new(cx.executor());
17068    fs.insert_tree(
17069        path!("/a"),
17070        json!({
17071            "main.rs": "fn main() { let a = 5; }",
17072            "other.rs": "// Test file",
17073        }),
17074    )
17075    .await;
17076
17077    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17078
17079    let server_restarts = Arc::new(AtomicUsize::new(0));
17080    let closure_restarts = Arc::clone(&server_restarts);
17081    let language_server_name = "test language server";
17082    let language_name: LanguageName = "Rust".into();
17083
17084    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17085    language_registry.add(Arc::new(Language::new(
17086        LanguageConfig {
17087            name: language_name.clone(),
17088            matcher: LanguageMatcher {
17089                path_suffixes: vec!["rs".to_string()],
17090                ..Default::default()
17091            },
17092            ..Default::default()
17093        },
17094        Some(tree_sitter_rust::LANGUAGE.into()),
17095    )));
17096    let mut fake_servers = language_registry.register_fake_lsp(
17097        "Rust",
17098        FakeLspAdapter {
17099            name: language_server_name,
17100            initialization_options: Some(json!({
17101                "testOptionValue": true
17102            })),
17103            initializer: Some(Box::new(move |fake_server| {
17104                let task_restarts = Arc::clone(&closure_restarts);
17105                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17106                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17107                    futures::future::ready(Ok(()))
17108                });
17109            })),
17110            ..Default::default()
17111        },
17112    );
17113
17114    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17115    let _buffer = project
17116        .update(cx, |project, cx| {
17117            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17118        })
17119        .await
17120        .unwrap();
17121    let _fake_server = fake_servers.next().await.unwrap();
17122    update_test_language_settings(cx, |language_settings| {
17123        language_settings.languages.0.insert(
17124            language_name.clone().0,
17125            LanguageSettingsContent {
17126                tab_size: NonZeroU32::new(8),
17127                ..Default::default()
17128            },
17129        );
17130    });
17131    cx.executor().run_until_parked();
17132    assert_eq!(
17133        server_restarts.load(atomic::Ordering::Acquire),
17134        0,
17135        "Should not restart LSP server on an unrelated change"
17136    );
17137
17138    update_test_project_settings(cx, |project_settings| {
17139        project_settings.lsp.insert(
17140            "Some other server name".into(),
17141            LspSettings {
17142                binary: None,
17143                settings: None,
17144                initialization_options: Some(json!({
17145                    "some other init value": false
17146                })),
17147                enable_lsp_tasks: false,
17148                fetch: None,
17149            },
17150        );
17151    });
17152    cx.executor().run_until_parked();
17153    assert_eq!(
17154        server_restarts.load(atomic::Ordering::Acquire),
17155        0,
17156        "Should not restart LSP server on an unrelated LSP settings change"
17157    );
17158
17159    update_test_project_settings(cx, |project_settings| {
17160        project_settings.lsp.insert(
17161            language_server_name.into(),
17162            LspSettings {
17163                binary: None,
17164                settings: None,
17165                initialization_options: Some(json!({
17166                    "anotherInitValue": false
17167                })),
17168                enable_lsp_tasks: false,
17169                fetch: None,
17170            },
17171        );
17172    });
17173    cx.executor().run_until_parked();
17174    assert_eq!(
17175        server_restarts.load(atomic::Ordering::Acquire),
17176        1,
17177        "Should restart LSP server on a related LSP settings change"
17178    );
17179
17180    update_test_project_settings(cx, |project_settings| {
17181        project_settings.lsp.insert(
17182            language_server_name.into(),
17183            LspSettings {
17184                binary: None,
17185                settings: None,
17186                initialization_options: Some(json!({
17187                    "anotherInitValue": false
17188                })),
17189                enable_lsp_tasks: false,
17190                fetch: None,
17191            },
17192        );
17193    });
17194    cx.executor().run_until_parked();
17195    assert_eq!(
17196        server_restarts.load(atomic::Ordering::Acquire),
17197        1,
17198        "Should not restart LSP server on a related LSP settings change that is the same"
17199    );
17200
17201    update_test_project_settings(cx, |project_settings| {
17202        project_settings.lsp.insert(
17203            language_server_name.into(),
17204            LspSettings {
17205                binary: None,
17206                settings: None,
17207                initialization_options: None,
17208                enable_lsp_tasks: false,
17209                fetch: None,
17210            },
17211        );
17212    });
17213    cx.executor().run_until_parked();
17214    assert_eq!(
17215        server_restarts.load(atomic::Ordering::Acquire),
17216        2,
17217        "Should restart LSP server on another related LSP settings change"
17218    );
17219}
17220
17221#[gpui::test]
17222async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17223    init_test(cx, |_| {});
17224
17225    let mut cx = EditorLspTestContext::new_rust(
17226        lsp::ServerCapabilities {
17227            completion_provider: Some(lsp::CompletionOptions {
17228                trigger_characters: Some(vec![".".to_string()]),
17229                resolve_provider: Some(true),
17230                ..Default::default()
17231            }),
17232            ..Default::default()
17233        },
17234        cx,
17235    )
17236    .await;
17237
17238    cx.set_state("fn main() { let a = 2ˇ; }");
17239    cx.simulate_keystroke(".");
17240    let completion_item = lsp::CompletionItem {
17241        label: "some".into(),
17242        kind: Some(lsp::CompletionItemKind::SNIPPET),
17243        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17244        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17245            kind: lsp::MarkupKind::Markdown,
17246            value: "```rust\nSome(2)\n```".to_string(),
17247        })),
17248        deprecated: Some(false),
17249        sort_text: Some("fffffff2".to_string()),
17250        filter_text: Some("some".to_string()),
17251        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17252        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17253            range: lsp::Range {
17254                start: lsp::Position {
17255                    line: 0,
17256                    character: 22,
17257                },
17258                end: lsp::Position {
17259                    line: 0,
17260                    character: 22,
17261                },
17262            },
17263            new_text: "Some(2)".to_string(),
17264        })),
17265        additional_text_edits: Some(vec![lsp::TextEdit {
17266            range: lsp::Range {
17267                start: lsp::Position {
17268                    line: 0,
17269                    character: 20,
17270                },
17271                end: lsp::Position {
17272                    line: 0,
17273                    character: 22,
17274                },
17275            },
17276            new_text: "".to_string(),
17277        }]),
17278        ..Default::default()
17279    };
17280
17281    let closure_completion_item = completion_item.clone();
17282    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17283        let task_completion_item = closure_completion_item.clone();
17284        async move {
17285            Ok(Some(lsp::CompletionResponse::Array(vec![
17286                task_completion_item,
17287            ])))
17288        }
17289    });
17290
17291    request.next().await;
17292
17293    cx.condition(|editor, _| editor.context_menu_visible())
17294        .await;
17295    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17296        editor
17297            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17298            .unwrap()
17299    });
17300    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17301
17302    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17303        let task_completion_item = completion_item.clone();
17304        async move { Ok(task_completion_item) }
17305    })
17306    .next()
17307    .await
17308    .unwrap();
17309    apply_additional_edits.await.unwrap();
17310    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17311}
17312
17313#[gpui::test]
17314async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17315    init_test(cx, |_| {});
17316
17317    let mut cx = EditorLspTestContext::new_rust(
17318        lsp::ServerCapabilities {
17319            completion_provider: Some(lsp::CompletionOptions {
17320                trigger_characters: Some(vec![".".to_string()]),
17321                resolve_provider: Some(true),
17322                ..Default::default()
17323            }),
17324            ..Default::default()
17325        },
17326        cx,
17327    )
17328    .await;
17329
17330    cx.set_state("fn main() { let a = 2ˇ; }");
17331    cx.simulate_keystroke(".");
17332
17333    let item1 = lsp::CompletionItem {
17334        label: "method id()".to_string(),
17335        filter_text: Some("id".to_string()),
17336        detail: None,
17337        documentation: None,
17338        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17339            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17340            new_text: ".id".to_string(),
17341        })),
17342        ..lsp::CompletionItem::default()
17343    };
17344
17345    let item2 = lsp::CompletionItem {
17346        label: "other".to_string(),
17347        filter_text: Some("other".to_string()),
17348        detail: None,
17349        documentation: None,
17350        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17351            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17352            new_text: ".other".to_string(),
17353        })),
17354        ..lsp::CompletionItem::default()
17355    };
17356
17357    let item1 = item1.clone();
17358    cx.set_request_handler::<lsp::request::Completion, _, _>({
17359        let item1 = item1.clone();
17360        move |_, _, _| {
17361            let item1 = item1.clone();
17362            let item2 = item2.clone();
17363            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17364        }
17365    })
17366    .next()
17367    .await;
17368
17369    cx.condition(|editor, _| editor.context_menu_visible())
17370        .await;
17371    cx.update_editor(|editor, _, _| {
17372        let context_menu = editor.context_menu.borrow_mut();
17373        let context_menu = context_menu
17374            .as_ref()
17375            .expect("Should have the context menu deployed");
17376        match context_menu {
17377            CodeContextMenu::Completions(completions_menu) => {
17378                let completions = completions_menu.completions.borrow_mut();
17379                assert_eq!(
17380                    completions
17381                        .iter()
17382                        .map(|completion| &completion.label.text)
17383                        .collect::<Vec<_>>(),
17384                    vec!["method id()", "other"]
17385                )
17386            }
17387            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17388        }
17389    });
17390
17391    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17392        let item1 = item1.clone();
17393        move |_, item_to_resolve, _| {
17394            let item1 = item1.clone();
17395            async move {
17396                if item1 == item_to_resolve {
17397                    Ok(lsp::CompletionItem {
17398                        label: "method id()".to_string(),
17399                        filter_text: Some("id".to_string()),
17400                        detail: Some("Now resolved!".to_string()),
17401                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17402                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17403                            range: lsp::Range::new(
17404                                lsp::Position::new(0, 22),
17405                                lsp::Position::new(0, 22),
17406                            ),
17407                            new_text: ".id".to_string(),
17408                        })),
17409                        ..lsp::CompletionItem::default()
17410                    })
17411                } else {
17412                    Ok(item_to_resolve)
17413                }
17414            }
17415        }
17416    })
17417    .next()
17418    .await
17419    .unwrap();
17420    cx.run_until_parked();
17421
17422    cx.update_editor(|editor, window, cx| {
17423        editor.context_menu_next(&Default::default(), window, cx);
17424    });
17425
17426    cx.update_editor(|editor, _, _| {
17427        let context_menu = editor.context_menu.borrow_mut();
17428        let context_menu = context_menu
17429            .as_ref()
17430            .expect("Should have the context menu deployed");
17431        match context_menu {
17432            CodeContextMenu::Completions(completions_menu) => {
17433                let completions = completions_menu.completions.borrow_mut();
17434                assert_eq!(
17435                    completions
17436                        .iter()
17437                        .map(|completion| &completion.label.text)
17438                        .collect::<Vec<_>>(),
17439                    vec!["method id() Now resolved!", "other"],
17440                    "Should update first completion label, but not second as the filter text did not match."
17441                );
17442            }
17443            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17444        }
17445    });
17446}
17447
17448#[gpui::test]
17449async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17450    init_test(cx, |_| {});
17451    let mut cx = EditorLspTestContext::new_rust(
17452        lsp::ServerCapabilities {
17453            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17454            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17455            completion_provider: Some(lsp::CompletionOptions {
17456                resolve_provider: Some(true),
17457                ..Default::default()
17458            }),
17459            ..Default::default()
17460        },
17461        cx,
17462    )
17463    .await;
17464    cx.set_state(indoc! {"
17465        struct TestStruct {
17466            field: i32
17467        }
17468
17469        fn mainˇ() {
17470            let unused_var = 42;
17471            let test_struct = TestStruct { field: 42 };
17472        }
17473    "});
17474    let symbol_range = cx.lsp_range(indoc! {"
17475        struct TestStruct {
17476            field: i32
17477        }
17478
17479        «fn main»() {
17480            let unused_var = 42;
17481            let test_struct = TestStruct { field: 42 };
17482        }
17483    "});
17484    let mut hover_requests =
17485        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17486            Ok(Some(lsp::Hover {
17487                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17488                    kind: lsp::MarkupKind::Markdown,
17489                    value: "Function documentation".to_string(),
17490                }),
17491                range: Some(symbol_range),
17492            }))
17493        });
17494
17495    // Case 1: Test that code action menu hide hover popover
17496    cx.dispatch_action(Hover);
17497    hover_requests.next().await;
17498    cx.condition(|editor, _| editor.hover_state.visible()).await;
17499    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17500        move |_, _, _| async move {
17501            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17502                lsp::CodeAction {
17503                    title: "Remove unused variable".to_string(),
17504                    kind: Some(CodeActionKind::QUICKFIX),
17505                    edit: Some(lsp::WorkspaceEdit {
17506                        changes: Some(
17507                            [(
17508                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17509                                vec![lsp::TextEdit {
17510                                    range: lsp::Range::new(
17511                                        lsp::Position::new(5, 4),
17512                                        lsp::Position::new(5, 27),
17513                                    ),
17514                                    new_text: "".to_string(),
17515                                }],
17516                            )]
17517                            .into_iter()
17518                            .collect(),
17519                        ),
17520                        ..Default::default()
17521                    }),
17522                    ..Default::default()
17523                },
17524            )]))
17525        },
17526    );
17527    cx.update_editor(|editor, window, cx| {
17528        editor.toggle_code_actions(
17529            &ToggleCodeActions {
17530                deployed_from: None,
17531                quick_launch: false,
17532            },
17533            window,
17534            cx,
17535        );
17536    });
17537    code_action_requests.next().await;
17538    cx.run_until_parked();
17539    cx.condition(|editor, _| editor.context_menu_visible())
17540        .await;
17541    cx.update_editor(|editor, _, _| {
17542        assert!(
17543            !editor.hover_state.visible(),
17544            "Hover popover should be hidden when code action menu is shown"
17545        );
17546        // Hide code actions
17547        editor.context_menu.take();
17548    });
17549
17550    // Case 2: Test that code completions hide hover popover
17551    cx.dispatch_action(Hover);
17552    hover_requests.next().await;
17553    cx.condition(|editor, _| editor.hover_state.visible()).await;
17554    let counter = Arc::new(AtomicUsize::new(0));
17555    let mut completion_requests =
17556        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17557            let counter = counter.clone();
17558            async move {
17559                counter.fetch_add(1, atomic::Ordering::Release);
17560                Ok(Some(lsp::CompletionResponse::Array(vec![
17561                    lsp::CompletionItem {
17562                        label: "main".into(),
17563                        kind: Some(lsp::CompletionItemKind::FUNCTION),
17564                        detail: Some("() -> ()".to_string()),
17565                        ..Default::default()
17566                    },
17567                    lsp::CompletionItem {
17568                        label: "TestStruct".into(),
17569                        kind: Some(lsp::CompletionItemKind::STRUCT),
17570                        detail: Some("struct TestStruct".to_string()),
17571                        ..Default::default()
17572                    },
17573                ])))
17574            }
17575        });
17576    cx.update_editor(|editor, window, cx| {
17577        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
17578    });
17579    completion_requests.next().await;
17580    cx.condition(|editor, _| editor.context_menu_visible())
17581        .await;
17582    cx.update_editor(|editor, _, _| {
17583        assert!(
17584            !editor.hover_state.visible(),
17585            "Hover popover should be hidden when completion menu is shown"
17586        );
17587    });
17588}
17589
17590#[gpui::test]
17591async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
17592    init_test(cx, |_| {});
17593
17594    let mut cx = EditorLspTestContext::new_rust(
17595        lsp::ServerCapabilities {
17596            completion_provider: Some(lsp::CompletionOptions {
17597                trigger_characters: Some(vec![".".to_string()]),
17598                resolve_provider: Some(true),
17599                ..Default::default()
17600            }),
17601            ..Default::default()
17602        },
17603        cx,
17604    )
17605    .await;
17606
17607    cx.set_state("fn main() { let a = 2ˇ; }");
17608    cx.simulate_keystroke(".");
17609
17610    let unresolved_item_1 = lsp::CompletionItem {
17611        label: "id".to_string(),
17612        filter_text: Some("id".to_string()),
17613        detail: None,
17614        documentation: None,
17615        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17616            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17617            new_text: ".id".to_string(),
17618        })),
17619        ..lsp::CompletionItem::default()
17620    };
17621    let resolved_item_1 = lsp::CompletionItem {
17622        additional_text_edits: Some(vec![lsp::TextEdit {
17623            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17624            new_text: "!!".to_string(),
17625        }]),
17626        ..unresolved_item_1.clone()
17627    };
17628    let unresolved_item_2 = lsp::CompletionItem {
17629        label: "other".to_string(),
17630        filter_text: Some("other".to_string()),
17631        detail: None,
17632        documentation: None,
17633        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17634            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17635            new_text: ".other".to_string(),
17636        })),
17637        ..lsp::CompletionItem::default()
17638    };
17639    let resolved_item_2 = lsp::CompletionItem {
17640        additional_text_edits: Some(vec![lsp::TextEdit {
17641            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17642            new_text: "??".to_string(),
17643        }]),
17644        ..unresolved_item_2.clone()
17645    };
17646
17647    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
17648    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
17649    cx.lsp
17650        .server
17651        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17652            let unresolved_item_1 = unresolved_item_1.clone();
17653            let resolved_item_1 = resolved_item_1.clone();
17654            let unresolved_item_2 = unresolved_item_2.clone();
17655            let resolved_item_2 = resolved_item_2.clone();
17656            let resolve_requests_1 = resolve_requests_1.clone();
17657            let resolve_requests_2 = resolve_requests_2.clone();
17658            move |unresolved_request, _| {
17659                let unresolved_item_1 = unresolved_item_1.clone();
17660                let resolved_item_1 = resolved_item_1.clone();
17661                let unresolved_item_2 = unresolved_item_2.clone();
17662                let resolved_item_2 = resolved_item_2.clone();
17663                let resolve_requests_1 = resolve_requests_1.clone();
17664                let resolve_requests_2 = resolve_requests_2.clone();
17665                async move {
17666                    if unresolved_request == unresolved_item_1 {
17667                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
17668                        Ok(resolved_item_1.clone())
17669                    } else if unresolved_request == unresolved_item_2 {
17670                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
17671                        Ok(resolved_item_2.clone())
17672                    } else {
17673                        panic!("Unexpected completion item {unresolved_request:?}")
17674                    }
17675                }
17676            }
17677        })
17678        .detach();
17679
17680    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17681        let unresolved_item_1 = unresolved_item_1.clone();
17682        let unresolved_item_2 = unresolved_item_2.clone();
17683        async move {
17684            Ok(Some(lsp::CompletionResponse::Array(vec![
17685                unresolved_item_1,
17686                unresolved_item_2,
17687            ])))
17688        }
17689    })
17690    .next()
17691    .await;
17692
17693    cx.condition(|editor, _| editor.context_menu_visible())
17694        .await;
17695    cx.update_editor(|editor, _, _| {
17696        let context_menu = editor.context_menu.borrow_mut();
17697        let context_menu = context_menu
17698            .as_ref()
17699            .expect("Should have the context menu deployed");
17700        match context_menu {
17701            CodeContextMenu::Completions(completions_menu) => {
17702                let completions = completions_menu.completions.borrow_mut();
17703                assert_eq!(
17704                    completions
17705                        .iter()
17706                        .map(|completion| &completion.label.text)
17707                        .collect::<Vec<_>>(),
17708                    vec!["id", "other"]
17709                )
17710            }
17711            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17712        }
17713    });
17714    cx.run_until_parked();
17715
17716    cx.update_editor(|editor, window, cx| {
17717        editor.context_menu_next(&ContextMenuNext, window, cx);
17718    });
17719    cx.run_until_parked();
17720    cx.update_editor(|editor, window, cx| {
17721        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17722    });
17723    cx.run_until_parked();
17724    cx.update_editor(|editor, window, cx| {
17725        editor.context_menu_next(&ContextMenuNext, window, cx);
17726    });
17727    cx.run_until_parked();
17728    cx.update_editor(|editor, window, cx| {
17729        editor
17730            .compose_completion(&ComposeCompletion::default(), window, cx)
17731            .expect("No task returned")
17732    })
17733    .await
17734    .expect("Completion failed");
17735    cx.run_until_parked();
17736
17737    cx.update_editor(|editor, _, cx| {
17738        assert_eq!(
17739            resolve_requests_1.load(atomic::Ordering::Acquire),
17740            1,
17741            "Should always resolve once despite multiple selections"
17742        );
17743        assert_eq!(
17744            resolve_requests_2.load(atomic::Ordering::Acquire),
17745            1,
17746            "Should always resolve once after multiple selections and applying the completion"
17747        );
17748        assert_eq!(
17749            editor.text(cx),
17750            "fn main() { let a = ??.other; }",
17751            "Should use resolved data when applying the completion"
17752        );
17753    });
17754}
17755
17756#[gpui::test]
17757async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
17758    init_test(cx, |_| {});
17759
17760    let item_0 = lsp::CompletionItem {
17761        label: "abs".into(),
17762        insert_text: Some("abs".into()),
17763        data: Some(json!({ "very": "special"})),
17764        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
17765        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
17766            lsp::InsertReplaceEdit {
17767                new_text: "abs".to_string(),
17768                insert: lsp::Range::default(),
17769                replace: lsp::Range::default(),
17770            },
17771        )),
17772        ..lsp::CompletionItem::default()
17773    };
17774    let items = iter::once(item_0.clone())
17775        .chain((11..51).map(|i| lsp::CompletionItem {
17776            label: format!("item_{}", i),
17777            insert_text: Some(format!("item_{}", i)),
17778            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
17779            ..lsp::CompletionItem::default()
17780        }))
17781        .collect::<Vec<_>>();
17782
17783    let default_commit_characters = vec!["?".to_string()];
17784    let default_data = json!({ "default": "data"});
17785    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
17786    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
17787    let default_edit_range = lsp::Range {
17788        start: lsp::Position {
17789            line: 0,
17790            character: 5,
17791        },
17792        end: lsp::Position {
17793            line: 0,
17794            character: 5,
17795        },
17796    };
17797
17798    let mut cx = EditorLspTestContext::new_rust(
17799        lsp::ServerCapabilities {
17800            completion_provider: Some(lsp::CompletionOptions {
17801                trigger_characters: Some(vec![".".to_string()]),
17802                resolve_provider: Some(true),
17803                ..Default::default()
17804            }),
17805            ..Default::default()
17806        },
17807        cx,
17808    )
17809    .await;
17810
17811    cx.set_state("fn main() { let a = 2ˇ; }");
17812    cx.simulate_keystroke(".");
17813
17814    let completion_data = default_data.clone();
17815    let completion_characters = default_commit_characters.clone();
17816    let completion_items = items.clone();
17817    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17818        let default_data = completion_data.clone();
17819        let default_commit_characters = completion_characters.clone();
17820        let items = completion_items.clone();
17821        async move {
17822            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
17823                items,
17824                item_defaults: Some(lsp::CompletionListItemDefaults {
17825                    data: Some(default_data.clone()),
17826                    commit_characters: Some(default_commit_characters.clone()),
17827                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
17828                        default_edit_range,
17829                    )),
17830                    insert_text_format: Some(default_insert_text_format),
17831                    insert_text_mode: Some(default_insert_text_mode),
17832                }),
17833                ..lsp::CompletionList::default()
17834            })))
17835        }
17836    })
17837    .next()
17838    .await;
17839
17840    let resolved_items = Arc::new(Mutex::new(Vec::new()));
17841    cx.lsp
17842        .server
17843        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17844            let closure_resolved_items = resolved_items.clone();
17845            move |item_to_resolve, _| {
17846                let closure_resolved_items = closure_resolved_items.clone();
17847                async move {
17848                    closure_resolved_items.lock().push(item_to_resolve.clone());
17849                    Ok(item_to_resolve)
17850                }
17851            }
17852        })
17853        .detach();
17854
17855    cx.condition(|editor, _| editor.context_menu_visible())
17856        .await;
17857    cx.run_until_parked();
17858    cx.update_editor(|editor, _, _| {
17859        let menu = editor.context_menu.borrow_mut();
17860        match menu.as_ref().expect("should have the completions menu") {
17861            CodeContextMenu::Completions(completions_menu) => {
17862                assert_eq!(
17863                    completions_menu
17864                        .entries
17865                        .borrow()
17866                        .iter()
17867                        .map(|mat| mat.string.clone())
17868                        .collect::<Vec<String>>(),
17869                    items
17870                        .iter()
17871                        .map(|completion| completion.label.clone())
17872                        .collect::<Vec<String>>()
17873                );
17874            }
17875            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
17876        }
17877    });
17878    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
17879    // with 4 from the end.
17880    assert_eq!(
17881        *resolved_items.lock(),
17882        [&items[0..16], &items[items.len() - 4..items.len()]]
17883            .concat()
17884            .iter()
17885            .cloned()
17886            .map(|mut item| {
17887                if item.data.is_none() {
17888                    item.data = Some(default_data.clone());
17889                }
17890                item
17891            })
17892            .collect::<Vec<lsp::CompletionItem>>(),
17893        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
17894    );
17895    resolved_items.lock().clear();
17896
17897    cx.update_editor(|editor, window, cx| {
17898        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17899    });
17900    cx.run_until_parked();
17901    // Completions that have already been resolved are skipped.
17902    assert_eq!(
17903        *resolved_items.lock(),
17904        items[items.len() - 17..items.len() - 4]
17905            .iter()
17906            .cloned()
17907            .map(|mut item| {
17908                if item.data.is_none() {
17909                    item.data = Some(default_data.clone());
17910                }
17911                item
17912            })
17913            .collect::<Vec<lsp::CompletionItem>>()
17914    );
17915    resolved_items.lock().clear();
17916}
17917
17918#[gpui::test]
17919async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
17920    init_test(cx, |_| {});
17921
17922    let mut cx = EditorLspTestContext::new(
17923        Language::new(
17924            LanguageConfig {
17925                matcher: LanguageMatcher {
17926                    path_suffixes: vec!["jsx".into()],
17927                    ..Default::default()
17928                },
17929                overrides: [(
17930                    "element".into(),
17931                    LanguageConfigOverride {
17932                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
17933                        ..Default::default()
17934                    },
17935                )]
17936                .into_iter()
17937                .collect(),
17938                ..Default::default()
17939            },
17940            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
17941        )
17942        .with_override_query("(jsx_self_closing_element) @element")
17943        .unwrap(),
17944        lsp::ServerCapabilities {
17945            completion_provider: Some(lsp::CompletionOptions {
17946                trigger_characters: Some(vec![":".to_string()]),
17947                ..Default::default()
17948            }),
17949            ..Default::default()
17950        },
17951        cx,
17952    )
17953    .await;
17954
17955    cx.lsp
17956        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
17957            Ok(Some(lsp::CompletionResponse::Array(vec![
17958                lsp::CompletionItem {
17959                    label: "bg-blue".into(),
17960                    ..Default::default()
17961                },
17962                lsp::CompletionItem {
17963                    label: "bg-red".into(),
17964                    ..Default::default()
17965                },
17966                lsp::CompletionItem {
17967                    label: "bg-yellow".into(),
17968                    ..Default::default()
17969                },
17970            ])))
17971        });
17972
17973    cx.set_state(r#"<p class="bgˇ" />"#);
17974
17975    // Trigger completion when typing a dash, because the dash is an extra
17976    // word character in the 'element' scope, which contains the cursor.
17977    cx.simulate_keystroke("-");
17978    cx.executor().run_until_parked();
17979    cx.update_editor(|editor, _, _| {
17980        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17981        {
17982            assert_eq!(
17983                completion_menu_entries(menu),
17984                &["bg-blue", "bg-red", "bg-yellow"]
17985            );
17986        } else {
17987            panic!("expected completion menu to be open");
17988        }
17989    });
17990
17991    cx.simulate_keystroke("l");
17992    cx.executor().run_until_parked();
17993    cx.update_editor(|editor, _, _| {
17994        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17995        {
17996            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
17997        } else {
17998            panic!("expected completion menu to be open");
17999        }
18000    });
18001
18002    // When filtering completions, consider the character after the '-' to
18003    // be the start of a subword.
18004    cx.set_state(r#"<p class="yelˇ" />"#);
18005    cx.simulate_keystroke("l");
18006    cx.executor().run_until_parked();
18007    cx.update_editor(|editor, _, _| {
18008        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18009        {
18010            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
18011        } else {
18012            panic!("expected completion menu to be open");
18013        }
18014    });
18015}
18016
18017fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
18018    let entries = menu.entries.borrow();
18019    entries.iter().map(|mat| mat.string.clone()).collect()
18020}
18021
18022#[gpui::test]
18023async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
18024    init_test(cx, |settings| {
18025        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
18026            Formatter::Prettier,
18027        )))
18028    });
18029
18030    let fs = FakeFs::new(cx.executor());
18031    fs.insert_file(path!("/file.ts"), Default::default()).await;
18032
18033    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
18034    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18035
18036    language_registry.add(Arc::new(Language::new(
18037        LanguageConfig {
18038            name: "TypeScript".into(),
18039            matcher: LanguageMatcher {
18040                path_suffixes: vec!["ts".to_string()],
18041                ..Default::default()
18042            },
18043            ..Default::default()
18044        },
18045        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18046    )));
18047    update_test_language_settings(cx, |settings| {
18048        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
18049    });
18050
18051    let test_plugin = "test_plugin";
18052    let _ = language_registry.register_fake_lsp(
18053        "TypeScript",
18054        FakeLspAdapter {
18055            prettier_plugins: vec![test_plugin],
18056            ..Default::default()
18057        },
18058    );
18059
18060    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
18061    let buffer = project
18062        .update(cx, |project, cx| {
18063            project.open_local_buffer(path!("/file.ts"), cx)
18064        })
18065        .await
18066        .unwrap();
18067
18068    let buffer_text = "one\ntwo\nthree\n";
18069    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18070    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18071    editor.update_in(cx, |editor, window, cx| {
18072        editor.set_text(buffer_text, window, cx)
18073    });
18074
18075    editor
18076        .update_in(cx, |editor, window, cx| {
18077            editor.perform_format(
18078                project.clone(),
18079                FormatTrigger::Manual,
18080                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18081                window,
18082                cx,
18083            )
18084        })
18085        .unwrap()
18086        .await;
18087    assert_eq!(
18088        editor.update(cx, |editor, cx| editor.text(cx)),
18089        buffer_text.to_string() + prettier_format_suffix,
18090        "Test prettier formatting was not applied to the original buffer text",
18091    );
18092
18093    update_test_language_settings(cx, |settings| {
18094        settings.defaults.formatter = Some(SelectedFormatter::Auto)
18095    });
18096    let format = editor.update_in(cx, |editor, window, cx| {
18097        editor.perform_format(
18098            project.clone(),
18099            FormatTrigger::Manual,
18100            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18101            window,
18102            cx,
18103        )
18104    });
18105    format.await.unwrap();
18106    assert_eq!(
18107        editor.update(cx, |editor, cx| editor.text(cx)),
18108        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18109        "Autoformatting (via test prettier) was not applied to the original buffer text",
18110    );
18111}
18112
18113#[gpui::test]
18114async fn test_addition_reverts(cx: &mut TestAppContext) {
18115    init_test(cx, |_| {});
18116    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18117    let base_text = indoc! {r#"
18118        struct Row;
18119        struct Row1;
18120        struct Row2;
18121
18122        struct Row4;
18123        struct Row5;
18124        struct Row6;
18125
18126        struct Row8;
18127        struct Row9;
18128        struct Row10;"#};
18129
18130    // When addition hunks are not adjacent to carets, no hunk revert is performed
18131    assert_hunk_revert(
18132        indoc! {r#"struct Row;
18133                   struct Row1;
18134                   struct Row1.1;
18135                   struct Row1.2;
18136                   struct Row2;ˇ
18137
18138                   struct Row4;
18139                   struct Row5;
18140                   struct Row6;
18141
18142                   struct Row8;
18143                   ˇstruct Row9;
18144                   struct Row9.1;
18145                   struct Row9.2;
18146                   struct Row9.3;
18147                   struct Row10;"#},
18148        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18149        indoc! {r#"struct Row;
18150                   struct Row1;
18151                   struct Row1.1;
18152                   struct Row1.2;
18153                   struct Row2;ˇ
18154
18155                   struct Row4;
18156                   struct Row5;
18157                   struct Row6;
18158
18159                   struct Row8;
18160                   ˇstruct Row9;
18161                   struct Row9.1;
18162                   struct Row9.2;
18163                   struct Row9.3;
18164                   struct Row10;"#},
18165        base_text,
18166        &mut cx,
18167    );
18168    // Same for selections
18169    assert_hunk_revert(
18170        indoc! {r#"struct Row;
18171                   struct Row1;
18172                   struct Row2;
18173                   struct Row2.1;
18174                   struct Row2.2;
18175                   «ˇ
18176                   struct Row4;
18177                   struct» Row5;
18178                   «struct Row6;
18179                   ˇ»
18180                   struct Row9.1;
18181                   struct Row9.2;
18182                   struct Row9.3;
18183                   struct Row8;
18184                   struct Row9;
18185                   struct Row10;"#},
18186        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18187        indoc! {r#"struct Row;
18188                   struct Row1;
18189                   struct Row2;
18190                   struct Row2.1;
18191                   struct Row2.2;
18192                   «ˇ
18193                   struct Row4;
18194                   struct» Row5;
18195                   «struct Row6;
18196                   ˇ»
18197                   struct Row9.1;
18198                   struct Row9.2;
18199                   struct Row9.3;
18200                   struct Row8;
18201                   struct Row9;
18202                   struct Row10;"#},
18203        base_text,
18204        &mut cx,
18205    );
18206
18207    // When carets and selections intersect the addition hunks, those are reverted.
18208    // Adjacent carets got merged.
18209    assert_hunk_revert(
18210        indoc! {r#"struct Row;
18211                   ˇ// something on the top
18212                   struct Row1;
18213                   struct Row2;
18214                   struct Roˇw3.1;
18215                   struct Row2.2;
18216                   struct Row2.3;ˇ
18217
18218                   struct Row4;
18219                   struct ˇRow5.1;
18220                   struct Row5.2;
18221                   struct «Rowˇ»5.3;
18222                   struct Row5;
18223                   struct Row6;
18224                   ˇ
18225                   struct Row9.1;
18226                   struct «Rowˇ»9.2;
18227                   struct «ˇRow»9.3;
18228                   struct Row8;
18229                   struct Row9;
18230                   «ˇ// something on bottom»
18231                   struct Row10;"#},
18232        vec![
18233            DiffHunkStatusKind::Added,
18234            DiffHunkStatusKind::Added,
18235            DiffHunkStatusKind::Added,
18236            DiffHunkStatusKind::Added,
18237            DiffHunkStatusKind::Added,
18238        ],
18239        indoc! {r#"struct Row;
18240                   ˇstruct Row1;
18241                   struct Row2;
18242                   ˇ
18243                   struct Row4;
18244                   ˇstruct Row5;
18245                   struct Row6;
18246                   ˇ
18247                   ˇstruct Row8;
18248                   struct Row9;
18249                   ˇstruct Row10;"#},
18250        base_text,
18251        &mut cx,
18252    );
18253}
18254
18255#[gpui::test]
18256async fn test_modification_reverts(cx: &mut TestAppContext) {
18257    init_test(cx, |_| {});
18258    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18259    let base_text = indoc! {r#"
18260        struct Row;
18261        struct Row1;
18262        struct Row2;
18263
18264        struct Row4;
18265        struct Row5;
18266        struct Row6;
18267
18268        struct Row8;
18269        struct Row9;
18270        struct Row10;"#};
18271
18272    // Modification hunks behave the same as the addition ones.
18273    assert_hunk_revert(
18274        indoc! {r#"struct Row;
18275                   struct Row1;
18276                   struct Row33;
18277                   ˇ
18278                   struct Row4;
18279                   struct Row5;
18280                   struct Row6;
18281                   ˇ
18282                   struct Row99;
18283                   struct Row9;
18284                   struct Row10;"#},
18285        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18286        indoc! {r#"struct Row;
18287                   struct Row1;
18288                   struct Row33;
18289                   ˇ
18290                   struct Row4;
18291                   struct Row5;
18292                   struct Row6;
18293                   ˇ
18294                   struct Row99;
18295                   struct Row9;
18296                   struct Row10;"#},
18297        base_text,
18298        &mut cx,
18299    );
18300    assert_hunk_revert(
18301        indoc! {r#"struct Row;
18302                   struct Row1;
18303                   struct Row33;
18304                   «ˇ
18305                   struct Row4;
18306                   struct» Row5;
18307                   «struct Row6;
18308                   ˇ»
18309                   struct Row99;
18310                   struct Row9;
18311                   struct Row10;"#},
18312        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18313        indoc! {r#"struct Row;
18314                   struct Row1;
18315                   struct Row33;
18316                   «ˇ
18317                   struct Row4;
18318                   struct» Row5;
18319                   «struct Row6;
18320                   ˇ»
18321                   struct Row99;
18322                   struct Row9;
18323                   struct Row10;"#},
18324        base_text,
18325        &mut cx,
18326    );
18327
18328    assert_hunk_revert(
18329        indoc! {r#"ˇstruct Row1.1;
18330                   struct Row1;
18331                   «ˇstr»uct Row22;
18332
18333                   struct ˇRow44;
18334                   struct Row5;
18335                   struct «Rˇ»ow66;ˇ
18336
18337                   «struˇ»ct Row88;
18338                   struct Row9;
18339                   struct Row1011;ˇ"#},
18340        vec![
18341            DiffHunkStatusKind::Modified,
18342            DiffHunkStatusKind::Modified,
18343            DiffHunkStatusKind::Modified,
18344            DiffHunkStatusKind::Modified,
18345            DiffHunkStatusKind::Modified,
18346            DiffHunkStatusKind::Modified,
18347        ],
18348        indoc! {r#"struct Row;
18349                   ˇstruct Row1;
18350                   struct Row2;
18351                   ˇ
18352                   struct Row4;
18353                   ˇstruct Row5;
18354                   struct Row6;
18355                   ˇ
18356                   struct Row8;
18357                   ˇstruct Row9;
18358                   struct Row10;ˇ"#},
18359        base_text,
18360        &mut cx,
18361    );
18362}
18363
18364#[gpui::test]
18365async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18366    init_test(cx, |_| {});
18367    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18368    let base_text = indoc! {r#"
18369        one
18370
18371        two
18372        three
18373        "#};
18374
18375    cx.set_head_text(base_text);
18376    cx.set_state("\nˇ\n");
18377    cx.executor().run_until_parked();
18378    cx.update_editor(|editor, _window, cx| {
18379        editor.expand_selected_diff_hunks(cx);
18380    });
18381    cx.executor().run_until_parked();
18382    cx.update_editor(|editor, window, cx| {
18383        editor.backspace(&Default::default(), window, cx);
18384    });
18385    cx.run_until_parked();
18386    cx.assert_state_with_diff(
18387        indoc! {r#"
18388
18389        - two
18390        - threeˇ
18391        +
18392        "#}
18393        .to_string(),
18394    );
18395}
18396
18397#[gpui::test]
18398async fn test_deletion_reverts(cx: &mut TestAppContext) {
18399    init_test(cx, |_| {});
18400    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18401    let base_text = indoc! {r#"struct Row;
18402struct Row1;
18403struct Row2;
18404
18405struct Row4;
18406struct Row5;
18407struct Row6;
18408
18409struct Row8;
18410struct Row9;
18411struct Row10;"#};
18412
18413    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18414    assert_hunk_revert(
18415        indoc! {r#"struct Row;
18416                   struct Row2;
18417
18418                   ˇstruct Row4;
18419                   struct Row5;
18420                   struct Row6;
18421                   ˇ
18422                   struct Row8;
18423                   struct Row10;"#},
18424        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18425        indoc! {r#"struct Row;
18426                   struct Row2;
18427
18428                   ˇstruct Row4;
18429                   struct Row5;
18430                   struct Row6;
18431                   ˇ
18432                   struct Row8;
18433                   struct Row10;"#},
18434        base_text,
18435        &mut cx,
18436    );
18437    assert_hunk_revert(
18438        indoc! {r#"struct Row;
18439                   struct Row2;
18440
18441                   «ˇstruct Row4;
18442                   struct» Row5;
18443                   «struct Row6;
18444                   ˇ»
18445                   struct Row8;
18446                   struct Row10;"#},
18447        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18448        indoc! {r#"struct Row;
18449                   struct Row2;
18450
18451                   «ˇstruct Row4;
18452                   struct» Row5;
18453                   «struct Row6;
18454                   ˇ»
18455                   struct Row8;
18456                   struct Row10;"#},
18457        base_text,
18458        &mut cx,
18459    );
18460
18461    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18462    assert_hunk_revert(
18463        indoc! {r#"struct Row;
18464                   ˇstruct Row2;
18465
18466                   struct Row4;
18467                   struct Row5;
18468                   struct Row6;
18469
18470                   struct Row8;ˇ
18471                   struct Row10;"#},
18472        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18473        indoc! {r#"struct Row;
18474                   struct Row1;
18475                   ˇstruct Row2;
18476
18477                   struct Row4;
18478                   struct Row5;
18479                   struct Row6;
18480
18481                   struct Row8;ˇ
18482                   struct Row9;
18483                   struct Row10;"#},
18484        base_text,
18485        &mut cx,
18486    );
18487    assert_hunk_revert(
18488        indoc! {r#"struct Row;
18489                   struct Row2«ˇ;
18490                   struct Row4;
18491                   struct» Row5;
18492                   «struct Row6;
18493
18494                   struct Row8;ˇ»
18495                   struct Row10;"#},
18496        vec![
18497            DiffHunkStatusKind::Deleted,
18498            DiffHunkStatusKind::Deleted,
18499            DiffHunkStatusKind::Deleted,
18500        ],
18501        indoc! {r#"struct Row;
18502                   struct Row1;
18503                   struct Row2«ˇ;
18504
18505                   struct Row4;
18506                   struct» Row5;
18507                   «struct Row6;
18508
18509                   struct Row8;ˇ»
18510                   struct Row9;
18511                   struct Row10;"#},
18512        base_text,
18513        &mut cx,
18514    );
18515}
18516
18517#[gpui::test]
18518async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18519    init_test(cx, |_| {});
18520
18521    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18522    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18523    let base_text_3 =
18524        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18525
18526    let text_1 = edit_first_char_of_every_line(base_text_1);
18527    let text_2 = edit_first_char_of_every_line(base_text_2);
18528    let text_3 = edit_first_char_of_every_line(base_text_3);
18529
18530    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18531    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18532    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18533
18534    let multibuffer = cx.new(|cx| {
18535        let mut multibuffer = MultiBuffer::new(ReadWrite);
18536        multibuffer.push_excerpts(
18537            buffer_1.clone(),
18538            [
18539                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18540                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18541                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18542            ],
18543            cx,
18544        );
18545        multibuffer.push_excerpts(
18546            buffer_2.clone(),
18547            [
18548                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18549                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18550                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18551            ],
18552            cx,
18553        );
18554        multibuffer.push_excerpts(
18555            buffer_3.clone(),
18556            [
18557                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18558                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18559                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18560            ],
18561            cx,
18562        );
18563        multibuffer
18564    });
18565
18566    let fs = FakeFs::new(cx.executor());
18567    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
18568    let (editor, cx) = cx
18569        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
18570    editor.update_in(cx, |editor, _window, cx| {
18571        for (buffer, diff_base) in [
18572            (buffer_1.clone(), base_text_1),
18573            (buffer_2.clone(), base_text_2),
18574            (buffer_3.clone(), base_text_3),
18575        ] {
18576            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18577            editor
18578                .buffer
18579                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18580        }
18581    });
18582    cx.executor().run_until_parked();
18583
18584    editor.update_in(cx, |editor, window, cx| {
18585        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}");
18586        editor.select_all(&SelectAll, window, cx);
18587        editor.git_restore(&Default::default(), window, cx);
18588    });
18589    cx.executor().run_until_parked();
18590
18591    // When all ranges are selected, all buffer hunks are reverted.
18592    editor.update(cx, |editor, cx| {
18593        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");
18594    });
18595    buffer_1.update(cx, |buffer, _| {
18596        assert_eq!(buffer.text(), base_text_1);
18597    });
18598    buffer_2.update(cx, |buffer, _| {
18599        assert_eq!(buffer.text(), base_text_2);
18600    });
18601    buffer_3.update(cx, |buffer, _| {
18602        assert_eq!(buffer.text(), base_text_3);
18603    });
18604
18605    editor.update_in(cx, |editor, window, cx| {
18606        editor.undo(&Default::default(), window, cx);
18607    });
18608
18609    editor.update_in(cx, |editor, window, cx| {
18610        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18611            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
18612        });
18613        editor.git_restore(&Default::default(), window, cx);
18614    });
18615
18616    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
18617    // but not affect buffer_2 and its related excerpts.
18618    editor.update(cx, |editor, cx| {
18619        assert_eq!(
18620            editor.text(cx),
18621            "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}"
18622        );
18623    });
18624    buffer_1.update(cx, |buffer, _| {
18625        assert_eq!(buffer.text(), base_text_1);
18626    });
18627    buffer_2.update(cx, |buffer, _| {
18628        assert_eq!(
18629            buffer.text(),
18630            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
18631        );
18632    });
18633    buffer_3.update(cx, |buffer, _| {
18634        assert_eq!(
18635            buffer.text(),
18636            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
18637        );
18638    });
18639
18640    fn edit_first_char_of_every_line(text: &str) -> String {
18641        text.split('\n')
18642            .map(|line| format!("X{}", &line[1..]))
18643            .collect::<Vec<_>>()
18644            .join("\n")
18645    }
18646}
18647
18648#[gpui::test]
18649async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
18650    init_test(cx, |_| {});
18651
18652    let cols = 4;
18653    let rows = 10;
18654    let sample_text_1 = sample_text(rows, cols, 'a');
18655    assert_eq!(
18656        sample_text_1,
18657        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
18658    );
18659    let sample_text_2 = sample_text(rows, cols, 'l');
18660    assert_eq!(
18661        sample_text_2,
18662        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
18663    );
18664    let sample_text_3 = sample_text(rows, cols, 'v');
18665    assert_eq!(
18666        sample_text_3,
18667        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
18668    );
18669
18670    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
18671    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
18672    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
18673
18674    let multi_buffer = cx.new(|cx| {
18675        let mut multibuffer = MultiBuffer::new(ReadWrite);
18676        multibuffer.push_excerpts(
18677            buffer_1.clone(),
18678            [
18679                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18680                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18681                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18682            ],
18683            cx,
18684        );
18685        multibuffer.push_excerpts(
18686            buffer_2.clone(),
18687            [
18688                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18689                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18690                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18691            ],
18692            cx,
18693        );
18694        multibuffer.push_excerpts(
18695            buffer_3.clone(),
18696            [
18697                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18698                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18699                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18700            ],
18701            cx,
18702        );
18703        multibuffer
18704    });
18705
18706    let fs = FakeFs::new(cx.executor());
18707    fs.insert_tree(
18708        "/a",
18709        json!({
18710            "main.rs": sample_text_1,
18711            "other.rs": sample_text_2,
18712            "lib.rs": sample_text_3,
18713        }),
18714    )
18715    .await;
18716    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18717    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18718    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18719    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18720        Editor::new(
18721            EditorMode::full(),
18722            multi_buffer,
18723            Some(project.clone()),
18724            window,
18725            cx,
18726        )
18727    });
18728    let multibuffer_item_id = workspace
18729        .update(cx, |workspace, window, cx| {
18730            assert!(
18731                workspace.active_item(cx).is_none(),
18732                "active item should be None before the first item is added"
18733            );
18734            workspace.add_item_to_active_pane(
18735                Box::new(multi_buffer_editor.clone()),
18736                None,
18737                true,
18738                window,
18739                cx,
18740            );
18741            let active_item = workspace
18742                .active_item(cx)
18743                .expect("should have an active item after adding the multi buffer");
18744            assert!(
18745                !active_item.is_singleton(cx),
18746                "A multi buffer was expected to active after adding"
18747            );
18748            active_item.item_id()
18749        })
18750        .unwrap();
18751    cx.executor().run_until_parked();
18752
18753    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18754        editor.change_selections(
18755            SelectionEffects::scroll(Autoscroll::Next),
18756            window,
18757            cx,
18758            |s| s.select_ranges(Some(1..2)),
18759        );
18760        editor.open_excerpts(&OpenExcerpts, window, cx);
18761    });
18762    cx.executor().run_until_parked();
18763    let first_item_id = workspace
18764        .update(cx, |workspace, window, cx| {
18765            let active_item = workspace
18766                .active_item(cx)
18767                .expect("should have an active item after navigating into the 1st buffer");
18768            let first_item_id = active_item.item_id();
18769            assert_ne!(
18770                first_item_id, multibuffer_item_id,
18771                "Should navigate into the 1st buffer and activate it"
18772            );
18773            assert!(
18774                active_item.is_singleton(cx),
18775                "New active item should be a singleton buffer"
18776            );
18777            assert_eq!(
18778                active_item
18779                    .act_as::<Editor>(cx)
18780                    .expect("should have navigated into an editor for the 1st buffer")
18781                    .read(cx)
18782                    .text(cx),
18783                sample_text_1
18784            );
18785
18786            workspace
18787                .go_back(workspace.active_pane().downgrade(), window, cx)
18788                .detach_and_log_err(cx);
18789
18790            first_item_id
18791        })
18792        .unwrap();
18793    cx.executor().run_until_parked();
18794    workspace
18795        .update(cx, |workspace, _, cx| {
18796            let active_item = workspace
18797                .active_item(cx)
18798                .expect("should have an active item after navigating back");
18799            assert_eq!(
18800                active_item.item_id(),
18801                multibuffer_item_id,
18802                "Should navigate back to the multi buffer"
18803            );
18804            assert!(!active_item.is_singleton(cx));
18805        })
18806        .unwrap();
18807
18808    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18809        editor.change_selections(
18810            SelectionEffects::scroll(Autoscroll::Next),
18811            window,
18812            cx,
18813            |s| s.select_ranges(Some(39..40)),
18814        );
18815        editor.open_excerpts(&OpenExcerpts, window, cx);
18816    });
18817    cx.executor().run_until_parked();
18818    let second_item_id = workspace
18819        .update(cx, |workspace, window, cx| {
18820            let active_item = workspace
18821                .active_item(cx)
18822                .expect("should have an active item after navigating into the 2nd buffer");
18823            let second_item_id = active_item.item_id();
18824            assert_ne!(
18825                second_item_id, multibuffer_item_id,
18826                "Should navigate away from the multibuffer"
18827            );
18828            assert_ne!(
18829                second_item_id, first_item_id,
18830                "Should navigate into the 2nd buffer and activate it"
18831            );
18832            assert!(
18833                active_item.is_singleton(cx),
18834                "New active item should be a singleton buffer"
18835            );
18836            assert_eq!(
18837                active_item
18838                    .act_as::<Editor>(cx)
18839                    .expect("should have navigated into an editor")
18840                    .read(cx)
18841                    .text(cx),
18842                sample_text_2
18843            );
18844
18845            workspace
18846                .go_back(workspace.active_pane().downgrade(), window, cx)
18847                .detach_and_log_err(cx);
18848
18849            second_item_id
18850        })
18851        .unwrap();
18852    cx.executor().run_until_parked();
18853    workspace
18854        .update(cx, |workspace, _, cx| {
18855            let active_item = workspace
18856                .active_item(cx)
18857                .expect("should have an active item after navigating back from the 2nd buffer");
18858            assert_eq!(
18859                active_item.item_id(),
18860                multibuffer_item_id,
18861                "Should navigate back from the 2nd buffer to the multi buffer"
18862            );
18863            assert!(!active_item.is_singleton(cx));
18864        })
18865        .unwrap();
18866
18867    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18868        editor.change_selections(
18869            SelectionEffects::scroll(Autoscroll::Next),
18870            window,
18871            cx,
18872            |s| s.select_ranges(Some(70..70)),
18873        );
18874        editor.open_excerpts(&OpenExcerpts, window, cx);
18875    });
18876    cx.executor().run_until_parked();
18877    workspace
18878        .update(cx, |workspace, window, cx| {
18879            let active_item = workspace
18880                .active_item(cx)
18881                .expect("should have an active item after navigating into the 3rd buffer");
18882            let third_item_id = active_item.item_id();
18883            assert_ne!(
18884                third_item_id, multibuffer_item_id,
18885                "Should navigate into the 3rd buffer and activate it"
18886            );
18887            assert_ne!(third_item_id, first_item_id);
18888            assert_ne!(third_item_id, second_item_id);
18889            assert!(
18890                active_item.is_singleton(cx),
18891                "New active item should be a singleton buffer"
18892            );
18893            assert_eq!(
18894                active_item
18895                    .act_as::<Editor>(cx)
18896                    .expect("should have navigated into an editor")
18897                    .read(cx)
18898                    .text(cx),
18899                sample_text_3
18900            );
18901
18902            workspace
18903                .go_back(workspace.active_pane().downgrade(), window, cx)
18904                .detach_and_log_err(cx);
18905        })
18906        .unwrap();
18907    cx.executor().run_until_parked();
18908    workspace
18909        .update(cx, |workspace, _, cx| {
18910            let active_item = workspace
18911                .active_item(cx)
18912                .expect("should have an active item after navigating back from the 3rd buffer");
18913            assert_eq!(
18914                active_item.item_id(),
18915                multibuffer_item_id,
18916                "Should navigate back from the 3rd buffer to the multi buffer"
18917            );
18918            assert!(!active_item.is_singleton(cx));
18919        })
18920        .unwrap();
18921}
18922
18923#[gpui::test]
18924async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18925    init_test(cx, |_| {});
18926
18927    let mut cx = EditorTestContext::new(cx).await;
18928
18929    let diff_base = r#"
18930        use some::mod;
18931
18932        const A: u32 = 42;
18933
18934        fn main() {
18935            println!("hello");
18936
18937            println!("world");
18938        }
18939        "#
18940    .unindent();
18941
18942    cx.set_state(
18943        &r#"
18944        use some::modified;
18945
18946        ˇ
18947        fn main() {
18948            println!("hello there");
18949
18950            println!("around the");
18951            println!("world");
18952        }
18953        "#
18954        .unindent(),
18955    );
18956
18957    cx.set_head_text(&diff_base);
18958    executor.run_until_parked();
18959
18960    cx.update_editor(|editor, window, cx| {
18961        editor.go_to_next_hunk(&GoToHunk, window, cx);
18962        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18963    });
18964    executor.run_until_parked();
18965    cx.assert_state_with_diff(
18966        r#"
18967          use some::modified;
18968
18969
18970          fn main() {
18971        -     println!("hello");
18972        + ˇ    println!("hello there");
18973
18974              println!("around the");
18975              println!("world");
18976          }
18977        "#
18978        .unindent(),
18979    );
18980
18981    cx.update_editor(|editor, window, cx| {
18982        for _ in 0..2 {
18983            editor.go_to_next_hunk(&GoToHunk, window, cx);
18984            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18985        }
18986    });
18987    executor.run_until_parked();
18988    cx.assert_state_with_diff(
18989        r#"
18990        - use some::mod;
18991        + ˇuse some::modified;
18992
18993
18994          fn main() {
18995        -     println!("hello");
18996        +     println!("hello there");
18997
18998        +     println!("around the");
18999              println!("world");
19000          }
19001        "#
19002        .unindent(),
19003    );
19004
19005    cx.update_editor(|editor, window, cx| {
19006        editor.go_to_next_hunk(&GoToHunk, window, cx);
19007        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19008    });
19009    executor.run_until_parked();
19010    cx.assert_state_with_diff(
19011        r#"
19012        - use some::mod;
19013        + use some::modified;
19014
19015        - const A: u32 = 42;
19016          ˇ
19017          fn main() {
19018        -     println!("hello");
19019        +     println!("hello there");
19020
19021        +     println!("around the");
19022              println!("world");
19023          }
19024        "#
19025        .unindent(),
19026    );
19027
19028    cx.update_editor(|editor, window, cx| {
19029        editor.cancel(&Cancel, window, cx);
19030    });
19031
19032    cx.assert_state_with_diff(
19033        r#"
19034          use some::modified;
19035
19036          ˇ
19037          fn main() {
19038              println!("hello there");
19039
19040              println!("around the");
19041              println!("world");
19042          }
19043        "#
19044        .unindent(),
19045    );
19046}
19047
19048#[gpui::test]
19049async fn test_diff_base_change_with_expanded_diff_hunks(
19050    executor: BackgroundExecutor,
19051    cx: &mut TestAppContext,
19052) {
19053    init_test(cx, |_| {});
19054
19055    let mut cx = EditorTestContext::new(cx).await;
19056
19057    let diff_base = r#"
19058        use some::mod1;
19059        use some::mod2;
19060
19061        const A: u32 = 42;
19062        const B: u32 = 42;
19063        const C: u32 = 42;
19064
19065        fn main() {
19066            println!("hello");
19067
19068            println!("world");
19069        }
19070        "#
19071    .unindent();
19072
19073    cx.set_state(
19074        &r#"
19075        use some::mod2;
19076
19077        const A: u32 = 42;
19078        const C: u32 = 42;
19079
19080        fn main(ˇ) {
19081            //println!("hello");
19082
19083            println!("world");
19084            //
19085            //
19086        }
19087        "#
19088        .unindent(),
19089    );
19090
19091    cx.set_head_text(&diff_base);
19092    executor.run_until_parked();
19093
19094    cx.update_editor(|editor, window, cx| {
19095        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19096    });
19097    executor.run_until_parked();
19098    cx.assert_state_with_diff(
19099        r#"
19100        - use some::mod1;
19101          use some::mod2;
19102
19103          const A: u32 = 42;
19104        - const B: u32 = 42;
19105          const C: u32 = 42;
19106
19107          fn main(ˇ) {
19108        -     println!("hello");
19109        +     //println!("hello");
19110
19111              println!("world");
19112        +     //
19113        +     //
19114          }
19115        "#
19116        .unindent(),
19117    );
19118
19119    cx.set_head_text("new diff base!");
19120    executor.run_until_parked();
19121    cx.assert_state_with_diff(
19122        r#"
19123        - new diff base!
19124        + use some::mod2;
19125        +
19126        + const A: u32 = 42;
19127        + const C: u32 = 42;
19128        +
19129        + fn main(ˇ) {
19130        +     //println!("hello");
19131        +
19132        +     println!("world");
19133        +     //
19134        +     //
19135        + }
19136        "#
19137        .unindent(),
19138    );
19139}
19140
19141#[gpui::test]
19142async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19143    init_test(cx, |_| {});
19144
19145    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19146    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19147    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19148    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19149    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19150    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19151
19152    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19153    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19154    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19155
19156    let multi_buffer = cx.new(|cx| {
19157        let mut multibuffer = MultiBuffer::new(ReadWrite);
19158        multibuffer.push_excerpts(
19159            buffer_1.clone(),
19160            [
19161                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19162                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19163                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19164            ],
19165            cx,
19166        );
19167        multibuffer.push_excerpts(
19168            buffer_2.clone(),
19169            [
19170                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19171                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19172                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19173            ],
19174            cx,
19175        );
19176        multibuffer.push_excerpts(
19177            buffer_3.clone(),
19178            [
19179                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19180                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19181                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19182            ],
19183            cx,
19184        );
19185        multibuffer
19186    });
19187
19188    let editor =
19189        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19190    editor
19191        .update(cx, |editor, _window, cx| {
19192            for (buffer, diff_base) in [
19193                (buffer_1.clone(), file_1_old),
19194                (buffer_2.clone(), file_2_old),
19195                (buffer_3.clone(), file_3_old),
19196            ] {
19197                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19198                editor
19199                    .buffer
19200                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19201            }
19202        })
19203        .unwrap();
19204
19205    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19206    cx.run_until_parked();
19207
19208    cx.assert_editor_state(
19209        &"
19210            ˇaaa
19211            ccc
19212            ddd
19213
19214            ggg
19215            hhh
19216
19217
19218            lll
19219            mmm
19220            NNN
19221
19222            qqq
19223            rrr
19224
19225            uuu
19226            111
19227            222
19228            333
19229
19230            666
19231            777
19232
19233            000
19234            !!!"
19235        .unindent(),
19236    );
19237
19238    cx.update_editor(|editor, window, cx| {
19239        editor.select_all(&SelectAll, window, cx);
19240        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19241    });
19242    cx.executor().run_until_parked();
19243
19244    cx.assert_state_with_diff(
19245        "
19246            «aaa
19247          - bbb
19248            ccc
19249            ddd
19250
19251            ggg
19252            hhh
19253
19254
19255            lll
19256            mmm
19257          - nnn
19258          + NNN
19259
19260            qqq
19261            rrr
19262
19263            uuu
19264            111
19265            222
19266            333
19267
19268          + 666
19269            777
19270
19271            000
19272            !!!ˇ»"
19273            .unindent(),
19274    );
19275}
19276
19277#[gpui::test]
19278async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19279    init_test(cx, |_| {});
19280
19281    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19282    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19283
19284    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19285    let multi_buffer = cx.new(|cx| {
19286        let mut multibuffer = MultiBuffer::new(ReadWrite);
19287        multibuffer.push_excerpts(
19288            buffer.clone(),
19289            [
19290                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19291                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19292                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19293            ],
19294            cx,
19295        );
19296        multibuffer
19297    });
19298
19299    let editor =
19300        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19301    editor
19302        .update(cx, |editor, _window, cx| {
19303            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19304            editor
19305                .buffer
19306                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19307        })
19308        .unwrap();
19309
19310    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19311    cx.run_until_parked();
19312
19313    cx.update_editor(|editor, window, cx| {
19314        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19315    });
19316    cx.executor().run_until_parked();
19317
19318    // When the start of a hunk coincides with the start of its excerpt,
19319    // the hunk is expanded. When the start of a hunk is earlier than
19320    // the start of its excerpt, the hunk is not expanded.
19321    cx.assert_state_with_diff(
19322        "
19323            ˇaaa
19324          - bbb
19325          + BBB
19326
19327          - ddd
19328          - eee
19329          + DDD
19330          + EEE
19331            fff
19332
19333            iii
19334        "
19335        .unindent(),
19336    );
19337}
19338
19339#[gpui::test]
19340async fn test_edits_around_expanded_insertion_hunks(
19341    executor: BackgroundExecutor,
19342    cx: &mut TestAppContext,
19343) {
19344    init_test(cx, |_| {});
19345
19346    let mut cx = EditorTestContext::new(cx).await;
19347
19348    let diff_base = r#"
19349        use some::mod1;
19350        use some::mod2;
19351
19352        const A: u32 = 42;
19353
19354        fn main() {
19355            println!("hello");
19356
19357            println!("world");
19358        }
19359        "#
19360    .unindent();
19361    executor.run_until_parked();
19362    cx.set_state(
19363        &r#"
19364        use some::mod1;
19365        use some::mod2;
19366
19367        const A: u32 = 42;
19368        const B: u32 = 42;
19369        const C: u32 = 42;
19370        ˇ
19371
19372        fn main() {
19373            println!("hello");
19374
19375            println!("world");
19376        }
19377        "#
19378        .unindent(),
19379    );
19380
19381    cx.set_head_text(&diff_base);
19382    executor.run_until_parked();
19383
19384    cx.update_editor(|editor, window, cx| {
19385        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19386    });
19387    executor.run_until_parked();
19388
19389    cx.assert_state_with_diff(
19390        r#"
19391        use some::mod1;
19392        use some::mod2;
19393
19394        const A: u32 = 42;
19395      + const B: u32 = 42;
19396      + const C: u32 = 42;
19397      + ˇ
19398
19399        fn main() {
19400            println!("hello");
19401
19402            println!("world");
19403        }
19404      "#
19405        .unindent(),
19406    );
19407
19408    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19409    executor.run_until_parked();
19410
19411    cx.assert_state_with_diff(
19412        r#"
19413        use some::mod1;
19414        use some::mod2;
19415
19416        const A: u32 = 42;
19417      + const B: u32 = 42;
19418      + const C: u32 = 42;
19419      + const D: u32 = 42;
19420      + ˇ
19421
19422        fn main() {
19423            println!("hello");
19424
19425            println!("world");
19426        }
19427      "#
19428        .unindent(),
19429    );
19430
19431    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19432    executor.run_until_parked();
19433
19434    cx.assert_state_with_diff(
19435        r#"
19436        use some::mod1;
19437        use some::mod2;
19438
19439        const A: u32 = 42;
19440      + const B: u32 = 42;
19441      + const C: u32 = 42;
19442      + const D: u32 = 42;
19443      + const E: u32 = 42;
19444      + ˇ
19445
19446        fn main() {
19447            println!("hello");
19448
19449            println!("world");
19450        }
19451      "#
19452        .unindent(),
19453    );
19454
19455    cx.update_editor(|editor, window, cx| {
19456        editor.delete_line(&DeleteLine, window, cx);
19457    });
19458    executor.run_until_parked();
19459
19460    cx.assert_state_with_diff(
19461        r#"
19462        use some::mod1;
19463        use some::mod2;
19464
19465        const A: u32 = 42;
19466      + const B: u32 = 42;
19467      + const C: u32 = 42;
19468      + const D: u32 = 42;
19469      + const E: u32 = 42;
19470        ˇ
19471        fn main() {
19472            println!("hello");
19473
19474            println!("world");
19475        }
19476      "#
19477        .unindent(),
19478    );
19479
19480    cx.update_editor(|editor, window, cx| {
19481        editor.move_up(&MoveUp, window, cx);
19482        editor.delete_line(&DeleteLine, window, cx);
19483        editor.move_up(&MoveUp, window, cx);
19484        editor.delete_line(&DeleteLine, window, cx);
19485        editor.move_up(&MoveUp, window, cx);
19486        editor.delete_line(&DeleteLine, window, cx);
19487    });
19488    executor.run_until_parked();
19489    cx.assert_state_with_diff(
19490        r#"
19491        use some::mod1;
19492        use some::mod2;
19493
19494        const A: u32 = 42;
19495      + const B: u32 = 42;
19496        ˇ
19497        fn main() {
19498            println!("hello");
19499
19500            println!("world");
19501        }
19502      "#
19503        .unindent(),
19504    );
19505
19506    cx.update_editor(|editor, window, cx| {
19507        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19508        editor.delete_line(&DeleteLine, window, cx);
19509    });
19510    executor.run_until_parked();
19511    cx.assert_state_with_diff(
19512        r#"
19513        ˇ
19514        fn main() {
19515            println!("hello");
19516
19517            println!("world");
19518        }
19519      "#
19520        .unindent(),
19521    );
19522}
19523
19524#[gpui::test]
19525async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19526    init_test(cx, |_| {});
19527
19528    let mut cx = EditorTestContext::new(cx).await;
19529    cx.set_head_text(indoc! { "
19530        one
19531        two
19532        three
19533        four
19534        five
19535        "
19536    });
19537    cx.set_state(indoc! { "
19538        one
19539        ˇthree
19540        five
19541    "});
19542    cx.run_until_parked();
19543    cx.update_editor(|editor, window, cx| {
19544        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19545    });
19546    cx.assert_state_with_diff(
19547        indoc! { "
19548        one
19549      - two
19550        ˇthree
19551      - four
19552        five
19553    "}
19554        .to_string(),
19555    );
19556    cx.update_editor(|editor, window, cx| {
19557        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19558    });
19559
19560    cx.assert_state_with_diff(
19561        indoc! { "
19562        one
19563        ˇthree
19564        five
19565    "}
19566        .to_string(),
19567    );
19568
19569    cx.set_state(indoc! { "
19570        one
19571        ˇTWO
19572        three
19573        four
19574        five
19575    "});
19576    cx.run_until_parked();
19577    cx.update_editor(|editor, window, cx| {
19578        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19579    });
19580
19581    cx.assert_state_with_diff(
19582        indoc! { "
19583            one
19584          - two
19585          + ˇTWO
19586            three
19587            four
19588            five
19589        "}
19590        .to_string(),
19591    );
19592    cx.update_editor(|editor, window, cx| {
19593        editor.move_up(&Default::default(), window, cx);
19594        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19595    });
19596    cx.assert_state_with_diff(
19597        indoc! { "
19598            one
19599            ˇTWO
19600            three
19601            four
19602            five
19603        "}
19604        .to_string(),
19605    );
19606}
19607
19608#[gpui::test]
19609async fn test_edits_around_expanded_deletion_hunks(
19610    executor: BackgroundExecutor,
19611    cx: &mut TestAppContext,
19612) {
19613    init_test(cx, |_| {});
19614
19615    let mut cx = EditorTestContext::new(cx).await;
19616
19617    let diff_base = r#"
19618        use some::mod1;
19619        use some::mod2;
19620
19621        const A: u32 = 42;
19622        const B: u32 = 42;
19623        const C: u32 = 42;
19624
19625
19626        fn main() {
19627            println!("hello");
19628
19629            println!("world");
19630        }
19631    "#
19632    .unindent();
19633    executor.run_until_parked();
19634    cx.set_state(
19635        &r#"
19636        use some::mod1;
19637        use some::mod2;
19638
19639        ˇconst B: u32 = 42;
19640        const C: u32 = 42;
19641
19642
19643        fn main() {
19644            println!("hello");
19645
19646            println!("world");
19647        }
19648        "#
19649        .unindent(),
19650    );
19651
19652    cx.set_head_text(&diff_base);
19653    executor.run_until_parked();
19654
19655    cx.update_editor(|editor, window, cx| {
19656        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19657    });
19658    executor.run_until_parked();
19659
19660    cx.assert_state_with_diff(
19661        r#"
19662        use some::mod1;
19663        use some::mod2;
19664
19665      - const A: u32 = 42;
19666        ˇconst B: u32 = 42;
19667        const C: u32 = 42;
19668
19669
19670        fn main() {
19671            println!("hello");
19672
19673            println!("world");
19674        }
19675      "#
19676        .unindent(),
19677    );
19678
19679    cx.update_editor(|editor, window, cx| {
19680        editor.delete_line(&DeleteLine, window, cx);
19681    });
19682    executor.run_until_parked();
19683    cx.assert_state_with_diff(
19684        r#"
19685        use some::mod1;
19686        use some::mod2;
19687
19688      - const A: u32 = 42;
19689      - const B: u32 = 42;
19690        ˇconst C: u32 = 42;
19691
19692
19693        fn main() {
19694            println!("hello");
19695
19696            println!("world");
19697        }
19698      "#
19699        .unindent(),
19700    );
19701
19702    cx.update_editor(|editor, window, cx| {
19703        editor.delete_line(&DeleteLine, window, cx);
19704    });
19705    executor.run_until_parked();
19706    cx.assert_state_with_diff(
19707        r#"
19708        use some::mod1;
19709        use some::mod2;
19710
19711      - const A: u32 = 42;
19712      - const B: u32 = 42;
19713      - const C: u32 = 42;
19714        ˇ
19715
19716        fn main() {
19717            println!("hello");
19718
19719            println!("world");
19720        }
19721      "#
19722        .unindent(),
19723    );
19724
19725    cx.update_editor(|editor, window, cx| {
19726        editor.handle_input("replacement", window, cx);
19727    });
19728    executor.run_until_parked();
19729    cx.assert_state_with_diff(
19730        r#"
19731        use some::mod1;
19732        use some::mod2;
19733
19734      - const A: u32 = 42;
19735      - const B: u32 = 42;
19736      - const C: u32 = 42;
19737      -
19738      + replacementˇ
19739
19740        fn main() {
19741            println!("hello");
19742
19743            println!("world");
19744        }
19745      "#
19746        .unindent(),
19747    );
19748}
19749
19750#[gpui::test]
19751async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19752    init_test(cx, |_| {});
19753
19754    let mut cx = EditorTestContext::new(cx).await;
19755
19756    let base_text = r#"
19757        one
19758        two
19759        three
19760        four
19761        five
19762    "#
19763    .unindent();
19764    executor.run_until_parked();
19765    cx.set_state(
19766        &r#"
19767        one
19768        two
19769        fˇour
19770        five
19771        "#
19772        .unindent(),
19773    );
19774
19775    cx.set_head_text(&base_text);
19776    executor.run_until_parked();
19777
19778    cx.update_editor(|editor, window, cx| {
19779        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19780    });
19781    executor.run_until_parked();
19782
19783    cx.assert_state_with_diff(
19784        r#"
19785          one
19786          two
19787        - three
19788          fˇour
19789          five
19790        "#
19791        .unindent(),
19792    );
19793
19794    cx.update_editor(|editor, window, cx| {
19795        editor.backspace(&Backspace, window, cx);
19796        editor.backspace(&Backspace, window, cx);
19797    });
19798    executor.run_until_parked();
19799    cx.assert_state_with_diff(
19800        r#"
19801          one
19802          two
19803        - threeˇ
19804        - four
19805        + our
19806          five
19807        "#
19808        .unindent(),
19809    );
19810}
19811
19812#[gpui::test]
19813async fn test_edit_after_expanded_modification_hunk(
19814    executor: BackgroundExecutor,
19815    cx: &mut TestAppContext,
19816) {
19817    init_test(cx, |_| {});
19818
19819    let mut cx = EditorTestContext::new(cx).await;
19820
19821    let diff_base = r#"
19822        use some::mod1;
19823        use some::mod2;
19824
19825        const A: u32 = 42;
19826        const B: u32 = 42;
19827        const C: u32 = 42;
19828        const D: u32 = 42;
19829
19830
19831        fn main() {
19832            println!("hello");
19833
19834            println!("world");
19835        }"#
19836    .unindent();
19837
19838    cx.set_state(
19839        &r#"
19840        use some::mod1;
19841        use some::mod2;
19842
19843        const A: u32 = 42;
19844        const B: u32 = 42;
19845        const C: u32 = 43ˇ
19846        const D: u32 = 42;
19847
19848
19849        fn main() {
19850            println!("hello");
19851
19852            println!("world");
19853        }"#
19854        .unindent(),
19855    );
19856
19857    cx.set_head_text(&diff_base);
19858    executor.run_until_parked();
19859    cx.update_editor(|editor, window, cx| {
19860        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19861    });
19862    executor.run_until_parked();
19863
19864    cx.assert_state_with_diff(
19865        r#"
19866        use some::mod1;
19867        use some::mod2;
19868
19869        const A: u32 = 42;
19870        const B: u32 = 42;
19871      - const C: u32 = 42;
19872      + const C: u32 = 43ˇ
19873        const D: u32 = 42;
19874
19875
19876        fn main() {
19877            println!("hello");
19878
19879            println!("world");
19880        }"#
19881        .unindent(),
19882    );
19883
19884    cx.update_editor(|editor, window, cx| {
19885        editor.handle_input("\nnew_line\n", window, cx);
19886    });
19887    executor.run_until_parked();
19888
19889    cx.assert_state_with_diff(
19890        r#"
19891        use some::mod1;
19892        use some::mod2;
19893
19894        const A: u32 = 42;
19895        const B: u32 = 42;
19896      - const C: u32 = 42;
19897      + const C: u32 = 43
19898      + new_line
19899      + ˇ
19900        const D: u32 = 42;
19901
19902
19903        fn main() {
19904            println!("hello");
19905
19906            println!("world");
19907        }"#
19908        .unindent(),
19909    );
19910}
19911
19912#[gpui::test]
19913async fn test_stage_and_unstage_added_file_hunk(
19914    executor: BackgroundExecutor,
19915    cx: &mut TestAppContext,
19916) {
19917    init_test(cx, |_| {});
19918
19919    let mut cx = EditorTestContext::new(cx).await;
19920    cx.update_editor(|editor, _, cx| {
19921        editor.set_expand_all_diff_hunks(cx);
19922    });
19923
19924    let working_copy = r#"
19925            ˇfn main() {
19926                println!("hello, world!");
19927            }
19928        "#
19929    .unindent();
19930
19931    cx.set_state(&working_copy);
19932    executor.run_until_parked();
19933
19934    cx.assert_state_with_diff(
19935        r#"
19936            + ˇfn main() {
19937            +     println!("hello, world!");
19938            + }
19939        "#
19940        .unindent(),
19941    );
19942    cx.assert_index_text(None);
19943
19944    cx.update_editor(|editor, window, cx| {
19945        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19946    });
19947    executor.run_until_parked();
19948    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
19949    cx.assert_state_with_diff(
19950        r#"
19951            + ˇfn main() {
19952            +     println!("hello, world!");
19953            + }
19954        "#
19955        .unindent(),
19956    );
19957
19958    cx.update_editor(|editor, window, cx| {
19959        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19960    });
19961    executor.run_until_parked();
19962    cx.assert_index_text(None);
19963}
19964
19965async fn setup_indent_guides_editor(
19966    text: &str,
19967    cx: &mut TestAppContext,
19968) -> (BufferId, EditorTestContext) {
19969    init_test(cx, |_| {});
19970
19971    let mut cx = EditorTestContext::new(cx).await;
19972
19973    let buffer_id = cx.update_editor(|editor, window, cx| {
19974        editor.set_text(text, window, cx);
19975        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
19976
19977        buffer_ids[0]
19978    });
19979
19980    (buffer_id, cx)
19981}
19982
19983fn assert_indent_guides(
19984    range: Range<u32>,
19985    expected: Vec<IndentGuide>,
19986    active_indices: Option<Vec<usize>>,
19987    cx: &mut EditorTestContext,
19988) {
19989    let indent_guides = cx.update_editor(|editor, window, cx| {
19990        let snapshot = editor.snapshot(window, cx).display_snapshot;
19991        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
19992            editor,
19993            MultiBufferRow(range.start)..MultiBufferRow(range.end),
19994            true,
19995            &snapshot,
19996            cx,
19997        );
19998
19999        indent_guides.sort_by(|a, b| {
20000            a.depth.cmp(&b.depth).then(
20001                a.start_row
20002                    .cmp(&b.start_row)
20003                    .then(a.end_row.cmp(&b.end_row)),
20004            )
20005        });
20006        indent_guides
20007    });
20008
20009    if let Some(expected) = active_indices {
20010        let active_indices = cx.update_editor(|editor, window, cx| {
20011            let snapshot = editor.snapshot(window, cx).display_snapshot;
20012            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
20013        });
20014
20015        assert_eq!(
20016            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
20017            expected,
20018            "Active indent guide indices do not match"
20019        );
20020    }
20021
20022    assert_eq!(indent_guides, expected, "Indent guides do not match");
20023}
20024
20025fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
20026    IndentGuide {
20027        buffer_id,
20028        start_row: MultiBufferRow(start_row),
20029        end_row: MultiBufferRow(end_row),
20030        depth,
20031        tab_size: 4,
20032        settings: IndentGuideSettings {
20033            enabled: true,
20034            line_width: 1,
20035            active_line_width: 1,
20036            coloring: IndentGuideColoring::default(),
20037            background_coloring: IndentGuideBackgroundColoring::default(),
20038        },
20039    }
20040}
20041
20042#[gpui::test]
20043async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
20044    let (buffer_id, mut cx) = setup_indent_guides_editor(
20045        &"
20046        fn main() {
20047            let a = 1;
20048        }"
20049        .unindent(),
20050        cx,
20051    )
20052    .await;
20053
20054    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20055}
20056
20057#[gpui::test]
20058async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
20059    let (buffer_id, mut cx) = setup_indent_guides_editor(
20060        &"
20061        fn main() {
20062            let a = 1;
20063            let b = 2;
20064        }"
20065        .unindent(),
20066        cx,
20067    )
20068    .await;
20069
20070    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20071}
20072
20073#[gpui::test]
20074async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20075    let (buffer_id, mut cx) = setup_indent_guides_editor(
20076        &"
20077        fn main() {
20078            let a = 1;
20079            if a == 3 {
20080                let b = 2;
20081            } else {
20082                let c = 3;
20083            }
20084        }"
20085        .unindent(),
20086        cx,
20087    )
20088    .await;
20089
20090    assert_indent_guides(
20091        0..8,
20092        vec![
20093            indent_guide(buffer_id, 1, 6, 0),
20094            indent_guide(buffer_id, 3, 3, 1),
20095            indent_guide(buffer_id, 5, 5, 1),
20096        ],
20097        None,
20098        &mut cx,
20099    );
20100}
20101
20102#[gpui::test]
20103async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20104    let (buffer_id, mut cx) = setup_indent_guides_editor(
20105        &"
20106        fn main() {
20107            let a = 1;
20108                let b = 2;
20109            let c = 3;
20110        }"
20111        .unindent(),
20112        cx,
20113    )
20114    .await;
20115
20116    assert_indent_guides(
20117        0..5,
20118        vec![
20119            indent_guide(buffer_id, 1, 3, 0),
20120            indent_guide(buffer_id, 2, 2, 1),
20121        ],
20122        None,
20123        &mut cx,
20124    );
20125}
20126
20127#[gpui::test]
20128async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20129    let (buffer_id, mut cx) = setup_indent_guides_editor(
20130        &"
20131        fn main() {
20132            let a = 1;
20133
20134            let c = 3;
20135        }"
20136        .unindent(),
20137        cx,
20138    )
20139    .await;
20140
20141    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20142}
20143
20144#[gpui::test]
20145async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20146    let (buffer_id, mut cx) = setup_indent_guides_editor(
20147        &"
20148        fn main() {
20149            let a = 1;
20150
20151            let c = 3;
20152
20153            if a == 3 {
20154                let b = 2;
20155            } else {
20156                let c = 3;
20157            }
20158        }"
20159        .unindent(),
20160        cx,
20161    )
20162    .await;
20163
20164    assert_indent_guides(
20165        0..11,
20166        vec![
20167            indent_guide(buffer_id, 1, 9, 0),
20168            indent_guide(buffer_id, 6, 6, 1),
20169            indent_guide(buffer_id, 8, 8, 1),
20170        ],
20171        None,
20172        &mut cx,
20173    );
20174}
20175
20176#[gpui::test]
20177async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20178    let (buffer_id, mut cx) = setup_indent_guides_editor(
20179        &"
20180        fn main() {
20181            let a = 1;
20182
20183            let c = 3;
20184
20185            if a == 3 {
20186                let b = 2;
20187            } else {
20188                let c = 3;
20189            }
20190        }"
20191        .unindent(),
20192        cx,
20193    )
20194    .await;
20195
20196    assert_indent_guides(
20197        1..11,
20198        vec![
20199            indent_guide(buffer_id, 1, 9, 0),
20200            indent_guide(buffer_id, 6, 6, 1),
20201            indent_guide(buffer_id, 8, 8, 1),
20202        ],
20203        None,
20204        &mut cx,
20205    );
20206}
20207
20208#[gpui::test]
20209async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20210    let (buffer_id, mut cx) = setup_indent_guides_editor(
20211        &"
20212        fn main() {
20213            let a = 1;
20214
20215            let c = 3;
20216
20217            if a == 3 {
20218                let b = 2;
20219            } else {
20220                let c = 3;
20221            }
20222        }"
20223        .unindent(),
20224        cx,
20225    )
20226    .await;
20227
20228    assert_indent_guides(
20229        1..10,
20230        vec![
20231            indent_guide(buffer_id, 1, 9, 0),
20232            indent_guide(buffer_id, 6, 6, 1),
20233            indent_guide(buffer_id, 8, 8, 1),
20234        ],
20235        None,
20236        &mut cx,
20237    );
20238}
20239
20240#[gpui::test]
20241async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20242    let (buffer_id, mut cx) = setup_indent_guides_editor(
20243        &"
20244        fn main() {
20245            if a {
20246                b(
20247                    c,
20248                    d,
20249                )
20250            } else {
20251                e(
20252                    f
20253                )
20254            }
20255        }"
20256        .unindent(),
20257        cx,
20258    )
20259    .await;
20260
20261    assert_indent_guides(
20262        0..11,
20263        vec![
20264            indent_guide(buffer_id, 1, 10, 0),
20265            indent_guide(buffer_id, 2, 5, 1),
20266            indent_guide(buffer_id, 7, 9, 1),
20267            indent_guide(buffer_id, 3, 4, 2),
20268            indent_guide(buffer_id, 8, 8, 2),
20269        ],
20270        None,
20271        &mut cx,
20272    );
20273
20274    cx.update_editor(|editor, window, cx| {
20275        editor.fold_at(MultiBufferRow(2), window, cx);
20276        assert_eq!(
20277            editor.display_text(cx),
20278            "
20279            fn main() {
20280                if a {
20281                    b(⋯
20282                    )
20283                } else {
20284                    e(
20285                        f
20286                    )
20287                }
20288            }"
20289            .unindent()
20290        );
20291    });
20292
20293    assert_indent_guides(
20294        0..11,
20295        vec![
20296            indent_guide(buffer_id, 1, 10, 0),
20297            indent_guide(buffer_id, 2, 5, 1),
20298            indent_guide(buffer_id, 7, 9, 1),
20299            indent_guide(buffer_id, 8, 8, 2),
20300        ],
20301        None,
20302        &mut cx,
20303    );
20304}
20305
20306#[gpui::test]
20307async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20308    let (buffer_id, mut cx) = setup_indent_guides_editor(
20309        &"
20310        block1
20311            block2
20312                block3
20313                    block4
20314            block2
20315        block1
20316        block1"
20317            .unindent(),
20318        cx,
20319    )
20320    .await;
20321
20322    assert_indent_guides(
20323        1..10,
20324        vec![
20325            indent_guide(buffer_id, 1, 4, 0),
20326            indent_guide(buffer_id, 2, 3, 1),
20327            indent_guide(buffer_id, 3, 3, 2),
20328        ],
20329        None,
20330        &mut cx,
20331    );
20332}
20333
20334#[gpui::test]
20335async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20336    let (buffer_id, mut cx) = setup_indent_guides_editor(
20337        &"
20338        block1
20339            block2
20340                block3
20341
20342        block1
20343        block1"
20344            .unindent(),
20345        cx,
20346    )
20347    .await;
20348
20349    assert_indent_guides(
20350        0..6,
20351        vec![
20352            indent_guide(buffer_id, 1, 2, 0),
20353            indent_guide(buffer_id, 2, 2, 1),
20354        ],
20355        None,
20356        &mut cx,
20357    );
20358}
20359
20360#[gpui::test]
20361async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20362    let (buffer_id, mut cx) = setup_indent_guides_editor(
20363        &"
20364        function component() {
20365        \treturn (
20366        \t\t\t
20367        \t\t<div>
20368        \t\t\t<abc></abc>
20369        \t\t</div>
20370        \t)
20371        }"
20372        .unindent(),
20373        cx,
20374    )
20375    .await;
20376
20377    assert_indent_guides(
20378        0..8,
20379        vec![
20380            indent_guide(buffer_id, 1, 6, 0),
20381            indent_guide(buffer_id, 2, 5, 1),
20382            indent_guide(buffer_id, 4, 4, 2),
20383        ],
20384        None,
20385        &mut cx,
20386    );
20387}
20388
20389#[gpui::test]
20390async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20391    let (buffer_id, mut cx) = setup_indent_guides_editor(
20392        &"
20393        function component() {
20394        \treturn (
20395        \t
20396        \t\t<div>
20397        \t\t\t<abc></abc>
20398        \t\t</div>
20399        \t)
20400        }"
20401        .unindent(),
20402        cx,
20403    )
20404    .await;
20405
20406    assert_indent_guides(
20407        0..8,
20408        vec![
20409            indent_guide(buffer_id, 1, 6, 0),
20410            indent_guide(buffer_id, 2, 5, 1),
20411            indent_guide(buffer_id, 4, 4, 2),
20412        ],
20413        None,
20414        &mut cx,
20415    );
20416}
20417
20418#[gpui::test]
20419async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20420    let (buffer_id, mut cx) = setup_indent_guides_editor(
20421        &"
20422        block1
20423
20424
20425
20426            block2
20427        "
20428        .unindent(),
20429        cx,
20430    )
20431    .await;
20432
20433    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20434}
20435
20436#[gpui::test]
20437async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20438    let (buffer_id, mut cx) = setup_indent_guides_editor(
20439        &"
20440        def a:
20441        \tb = 3
20442        \tif True:
20443        \t\tc = 4
20444        \t\td = 5
20445        \tprint(b)
20446        "
20447        .unindent(),
20448        cx,
20449    )
20450    .await;
20451
20452    assert_indent_guides(
20453        0..6,
20454        vec![
20455            indent_guide(buffer_id, 1, 5, 0),
20456            indent_guide(buffer_id, 3, 4, 1),
20457        ],
20458        None,
20459        &mut cx,
20460    );
20461}
20462
20463#[gpui::test]
20464async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20465    let (buffer_id, mut cx) = setup_indent_guides_editor(
20466        &"
20467    fn main() {
20468        let a = 1;
20469    }"
20470        .unindent(),
20471        cx,
20472    )
20473    .await;
20474
20475    cx.update_editor(|editor, window, cx| {
20476        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20477            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20478        });
20479    });
20480
20481    assert_indent_guides(
20482        0..3,
20483        vec![indent_guide(buffer_id, 1, 1, 0)],
20484        Some(vec![0]),
20485        &mut cx,
20486    );
20487}
20488
20489#[gpui::test]
20490async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20491    let (buffer_id, mut cx) = setup_indent_guides_editor(
20492        &"
20493    fn main() {
20494        if 1 == 2 {
20495            let a = 1;
20496        }
20497    }"
20498        .unindent(),
20499        cx,
20500    )
20501    .await;
20502
20503    cx.update_editor(|editor, window, cx| {
20504        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20505            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20506        });
20507    });
20508
20509    assert_indent_guides(
20510        0..4,
20511        vec![
20512            indent_guide(buffer_id, 1, 3, 0),
20513            indent_guide(buffer_id, 2, 2, 1),
20514        ],
20515        Some(vec![1]),
20516        &mut cx,
20517    );
20518
20519    cx.update_editor(|editor, window, cx| {
20520        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20521            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20522        });
20523    });
20524
20525    assert_indent_guides(
20526        0..4,
20527        vec![
20528            indent_guide(buffer_id, 1, 3, 0),
20529            indent_guide(buffer_id, 2, 2, 1),
20530        ],
20531        Some(vec![1]),
20532        &mut cx,
20533    );
20534
20535    cx.update_editor(|editor, window, cx| {
20536        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20537            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20538        });
20539    });
20540
20541    assert_indent_guides(
20542        0..4,
20543        vec![
20544            indent_guide(buffer_id, 1, 3, 0),
20545            indent_guide(buffer_id, 2, 2, 1),
20546        ],
20547        Some(vec![0]),
20548        &mut cx,
20549    );
20550}
20551
20552#[gpui::test]
20553async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
20554    let (buffer_id, mut cx) = setup_indent_guides_editor(
20555        &"
20556    fn main() {
20557        let a = 1;
20558
20559        let b = 2;
20560    }"
20561        .unindent(),
20562        cx,
20563    )
20564    .await;
20565
20566    cx.update_editor(|editor, window, cx| {
20567        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20568            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20569        });
20570    });
20571
20572    assert_indent_guides(
20573        0..5,
20574        vec![indent_guide(buffer_id, 1, 3, 0)],
20575        Some(vec![0]),
20576        &mut cx,
20577    );
20578}
20579
20580#[gpui::test]
20581async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
20582    let (buffer_id, mut cx) = setup_indent_guides_editor(
20583        &"
20584    def m:
20585        a = 1
20586        pass"
20587            .unindent(),
20588        cx,
20589    )
20590    .await;
20591
20592    cx.update_editor(|editor, window, cx| {
20593        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20594            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20595        });
20596    });
20597
20598    assert_indent_guides(
20599        0..3,
20600        vec![indent_guide(buffer_id, 1, 2, 0)],
20601        Some(vec![0]),
20602        &mut cx,
20603    );
20604}
20605
20606#[gpui::test]
20607async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
20608    init_test(cx, |_| {});
20609    let mut cx = EditorTestContext::new(cx).await;
20610    let text = indoc! {
20611        "
20612        impl A {
20613            fn b() {
20614                0;
20615                3;
20616                5;
20617                6;
20618                7;
20619            }
20620        }
20621        "
20622    };
20623    let base_text = indoc! {
20624        "
20625        impl A {
20626            fn b() {
20627                0;
20628                1;
20629                2;
20630                3;
20631                4;
20632            }
20633            fn c() {
20634                5;
20635                6;
20636                7;
20637            }
20638        }
20639        "
20640    };
20641
20642    cx.update_editor(|editor, window, cx| {
20643        editor.set_text(text, window, cx);
20644
20645        editor.buffer().update(cx, |multibuffer, cx| {
20646            let buffer = multibuffer.as_singleton().unwrap();
20647            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
20648
20649            multibuffer.set_all_diff_hunks_expanded(cx);
20650            multibuffer.add_diff(diff, cx);
20651
20652            buffer.read(cx).remote_id()
20653        })
20654    });
20655    cx.run_until_parked();
20656
20657    cx.assert_state_with_diff(
20658        indoc! { "
20659          impl A {
20660              fn b() {
20661                  0;
20662        -         1;
20663        -         2;
20664                  3;
20665        -         4;
20666        -     }
20667        -     fn c() {
20668                  5;
20669                  6;
20670                  7;
20671              }
20672          }
20673          ˇ"
20674        }
20675        .to_string(),
20676    );
20677
20678    let mut actual_guides = cx.update_editor(|editor, window, cx| {
20679        editor
20680            .snapshot(window, cx)
20681            .buffer_snapshot
20682            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
20683            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
20684            .collect::<Vec<_>>()
20685    });
20686    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
20687    assert_eq!(
20688        actual_guides,
20689        vec![
20690            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
20691            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
20692            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
20693        ]
20694    );
20695}
20696
20697#[gpui::test]
20698async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20699    init_test(cx, |_| {});
20700    let mut cx = EditorTestContext::new(cx).await;
20701
20702    let diff_base = r#"
20703        a
20704        b
20705        c
20706        "#
20707    .unindent();
20708
20709    cx.set_state(
20710        &r#"
20711        ˇA
20712        b
20713        C
20714        "#
20715        .unindent(),
20716    );
20717    cx.set_head_text(&diff_base);
20718    cx.update_editor(|editor, window, cx| {
20719        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20720    });
20721    executor.run_until_parked();
20722
20723    let both_hunks_expanded = r#"
20724        - a
20725        + ˇA
20726          b
20727        - c
20728        + C
20729        "#
20730    .unindent();
20731
20732    cx.assert_state_with_diff(both_hunks_expanded.clone());
20733
20734    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20735        let snapshot = editor.snapshot(window, cx);
20736        let hunks = editor
20737            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20738            .collect::<Vec<_>>();
20739        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20740        let buffer_id = hunks[0].buffer_id;
20741        hunks
20742            .into_iter()
20743            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20744            .collect::<Vec<_>>()
20745    });
20746    assert_eq!(hunk_ranges.len(), 2);
20747
20748    cx.update_editor(|editor, _, cx| {
20749        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20750    });
20751    executor.run_until_parked();
20752
20753    let second_hunk_expanded = r#"
20754          ˇA
20755          b
20756        - c
20757        + C
20758        "#
20759    .unindent();
20760
20761    cx.assert_state_with_diff(second_hunk_expanded);
20762
20763    cx.update_editor(|editor, _, cx| {
20764        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20765    });
20766    executor.run_until_parked();
20767
20768    cx.assert_state_with_diff(both_hunks_expanded.clone());
20769
20770    cx.update_editor(|editor, _, cx| {
20771        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20772    });
20773    executor.run_until_parked();
20774
20775    let first_hunk_expanded = r#"
20776        - a
20777        + ˇA
20778          b
20779          C
20780        "#
20781    .unindent();
20782
20783    cx.assert_state_with_diff(first_hunk_expanded);
20784
20785    cx.update_editor(|editor, _, cx| {
20786        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20787    });
20788    executor.run_until_parked();
20789
20790    cx.assert_state_with_diff(both_hunks_expanded);
20791
20792    cx.set_state(
20793        &r#"
20794        ˇA
20795        b
20796        "#
20797        .unindent(),
20798    );
20799    cx.run_until_parked();
20800
20801    // TODO this cursor position seems bad
20802    cx.assert_state_with_diff(
20803        r#"
20804        - ˇa
20805        + A
20806          b
20807        "#
20808        .unindent(),
20809    );
20810
20811    cx.update_editor(|editor, window, cx| {
20812        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20813    });
20814
20815    cx.assert_state_with_diff(
20816        r#"
20817            - ˇa
20818            + A
20819              b
20820            - c
20821            "#
20822        .unindent(),
20823    );
20824
20825    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20826        let snapshot = editor.snapshot(window, cx);
20827        let hunks = editor
20828            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20829            .collect::<Vec<_>>();
20830        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20831        let buffer_id = hunks[0].buffer_id;
20832        hunks
20833            .into_iter()
20834            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20835            .collect::<Vec<_>>()
20836    });
20837    assert_eq!(hunk_ranges.len(), 2);
20838
20839    cx.update_editor(|editor, _, cx| {
20840        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20841    });
20842    executor.run_until_parked();
20843
20844    cx.assert_state_with_diff(
20845        r#"
20846        - ˇa
20847        + A
20848          b
20849        "#
20850        .unindent(),
20851    );
20852}
20853
20854#[gpui::test]
20855async fn test_toggle_deletion_hunk_at_start_of_file(
20856    executor: BackgroundExecutor,
20857    cx: &mut TestAppContext,
20858) {
20859    init_test(cx, |_| {});
20860    let mut cx = EditorTestContext::new(cx).await;
20861
20862    let diff_base = r#"
20863        a
20864        b
20865        c
20866        "#
20867    .unindent();
20868
20869    cx.set_state(
20870        &r#"
20871        ˇb
20872        c
20873        "#
20874        .unindent(),
20875    );
20876    cx.set_head_text(&diff_base);
20877    cx.update_editor(|editor, window, cx| {
20878        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20879    });
20880    executor.run_until_parked();
20881
20882    let hunk_expanded = r#"
20883        - a
20884          ˇb
20885          c
20886        "#
20887    .unindent();
20888
20889    cx.assert_state_with_diff(hunk_expanded.clone());
20890
20891    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20892        let snapshot = editor.snapshot(window, cx);
20893        let hunks = editor
20894            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20895            .collect::<Vec<_>>();
20896        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20897        let buffer_id = hunks[0].buffer_id;
20898        hunks
20899            .into_iter()
20900            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20901            .collect::<Vec<_>>()
20902    });
20903    assert_eq!(hunk_ranges.len(), 1);
20904
20905    cx.update_editor(|editor, _, cx| {
20906        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20907    });
20908    executor.run_until_parked();
20909
20910    let hunk_collapsed = r#"
20911          ˇb
20912          c
20913        "#
20914    .unindent();
20915
20916    cx.assert_state_with_diff(hunk_collapsed);
20917
20918    cx.update_editor(|editor, _, cx| {
20919        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20920    });
20921    executor.run_until_parked();
20922
20923    cx.assert_state_with_diff(hunk_expanded);
20924}
20925
20926#[gpui::test]
20927async fn test_display_diff_hunks(cx: &mut TestAppContext) {
20928    init_test(cx, |_| {});
20929
20930    let fs = FakeFs::new(cx.executor());
20931    fs.insert_tree(
20932        path!("/test"),
20933        json!({
20934            ".git": {},
20935            "file-1": "ONE\n",
20936            "file-2": "TWO\n",
20937            "file-3": "THREE\n",
20938        }),
20939    )
20940    .await;
20941
20942    fs.set_head_for_repo(
20943        path!("/test/.git").as_ref(),
20944        &[
20945            ("file-1", "one\n".into()),
20946            ("file-2", "two\n".into()),
20947            ("file-3", "three\n".into()),
20948        ],
20949        "deadbeef",
20950    );
20951
20952    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
20953    let mut buffers = vec![];
20954    for i in 1..=3 {
20955        let buffer = project
20956            .update(cx, |project, cx| {
20957                let path = format!(path!("/test/file-{}"), i);
20958                project.open_local_buffer(path, cx)
20959            })
20960            .await
20961            .unwrap();
20962        buffers.push(buffer);
20963    }
20964
20965    let multibuffer = cx.new(|cx| {
20966        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
20967        multibuffer.set_all_diff_hunks_expanded(cx);
20968        for buffer in &buffers {
20969            let snapshot = buffer.read(cx).snapshot();
20970            multibuffer.set_excerpts_for_path(
20971                PathKey::namespaced(
20972                    0,
20973                    buffer.read(cx).file().unwrap().path().as_unix_str().into(),
20974                ),
20975                buffer.clone(),
20976                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
20977                2,
20978                cx,
20979            );
20980        }
20981        multibuffer
20982    });
20983
20984    let editor = cx.add_window(|window, cx| {
20985        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
20986    });
20987    cx.run_until_parked();
20988
20989    let snapshot = editor
20990        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
20991        .unwrap();
20992    let hunks = snapshot
20993        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
20994        .map(|hunk| match hunk {
20995            DisplayDiffHunk::Unfolded {
20996                display_row_range, ..
20997            } => display_row_range,
20998            DisplayDiffHunk::Folded { .. } => unreachable!(),
20999        })
21000        .collect::<Vec<_>>();
21001    assert_eq!(
21002        hunks,
21003        [
21004            DisplayRow(2)..DisplayRow(4),
21005            DisplayRow(7)..DisplayRow(9),
21006            DisplayRow(12)..DisplayRow(14),
21007        ]
21008    );
21009}
21010
21011#[gpui::test]
21012async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
21013    init_test(cx, |_| {});
21014
21015    let mut cx = EditorTestContext::new(cx).await;
21016    cx.set_head_text(indoc! { "
21017        one
21018        two
21019        three
21020        four
21021        five
21022        "
21023    });
21024    cx.set_index_text(indoc! { "
21025        one
21026        two
21027        three
21028        four
21029        five
21030        "
21031    });
21032    cx.set_state(indoc! {"
21033        one
21034        TWO
21035        ˇTHREE
21036        FOUR
21037        five
21038    "});
21039    cx.run_until_parked();
21040    cx.update_editor(|editor, window, cx| {
21041        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21042    });
21043    cx.run_until_parked();
21044    cx.assert_index_text(Some(indoc! {"
21045        one
21046        TWO
21047        THREE
21048        FOUR
21049        five
21050    "}));
21051    cx.set_state(indoc! { "
21052        one
21053        TWO
21054        ˇTHREE-HUNDRED
21055        FOUR
21056        five
21057    "});
21058    cx.run_until_parked();
21059    cx.update_editor(|editor, window, cx| {
21060        let snapshot = editor.snapshot(window, cx);
21061        let hunks = editor
21062            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
21063            .collect::<Vec<_>>();
21064        assert_eq!(hunks.len(), 1);
21065        assert_eq!(
21066            hunks[0].status(),
21067            DiffHunkStatus {
21068                kind: DiffHunkStatusKind::Modified,
21069                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21070            }
21071        );
21072
21073        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21074    });
21075    cx.run_until_parked();
21076    cx.assert_index_text(Some(indoc! {"
21077        one
21078        TWO
21079        THREE-HUNDRED
21080        FOUR
21081        five
21082    "}));
21083}
21084
21085#[gpui::test]
21086fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21087    init_test(cx, |_| {});
21088
21089    let editor = cx.add_window(|window, cx| {
21090        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21091        build_editor(buffer, window, cx)
21092    });
21093
21094    let render_args = Arc::new(Mutex::new(None));
21095    let snapshot = editor
21096        .update(cx, |editor, window, cx| {
21097            let snapshot = editor.buffer().read(cx).snapshot(cx);
21098            let range =
21099                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21100
21101            struct RenderArgs {
21102                row: MultiBufferRow,
21103                folded: bool,
21104                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21105            }
21106
21107            let crease = Crease::inline(
21108                range,
21109                FoldPlaceholder::test(),
21110                {
21111                    let toggle_callback = render_args.clone();
21112                    move |row, folded, callback, _window, _cx| {
21113                        *toggle_callback.lock() = Some(RenderArgs {
21114                            row,
21115                            folded,
21116                            callback,
21117                        });
21118                        div()
21119                    }
21120                },
21121                |_row, _folded, _window, _cx| div(),
21122            );
21123
21124            editor.insert_creases(Some(crease), cx);
21125            let snapshot = editor.snapshot(window, cx);
21126            let _div =
21127                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21128            snapshot
21129        })
21130        .unwrap();
21131
21132    let render_args = render_args.lock().take().unwrap();
21133    assert_eq!(render_args.row, MultiBufferRow(1));
21134    assert!(!render_args.folded);
21135    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21136
21137    cx.update_window(*editor, |_, window, cx| {
21138        (render_args.callback)(true, window, cx)
21139    })
21140    .unwrap();
21141    let snapshot = editor
21142        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21143        .unwrap();
21144    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21145
21146    cx.update_window(*editor, |_, window, cx| {
21147        (render_args.callback)(false, window, cx)
21148    })
21149    .unwrap();
21150    let snapshot = editor
21151        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21152        .unwrap();
21153    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21154}
21155
21156#[gpui::test]
21157async fn test_input_text(cx: &mut TestAppContext) {
21158    init_test(cx, |_| {});
21159    let mut cx = EditorTestContext::new(cx).await;
21160
21161    cx.set_state(
21162        &r#"ˇone
21163        two
21164
21165        three
21166        fourˇ
21167        five
21168
21169        siˇx"#
21170            .unindent(),
21171    );
21172
21173    cx.dispatch_action(HandleInput(String::new()));
21174    cx.assert_editor_state(
21175        &r#"ˇone
21176        two
21177
21178        three
21179        fourˇ
21180        five
21181
21182        siˇx"#
21183            .unindent(),
21184    );
21185
21186    cx.dispatch_action(HandleInput("AAAA".to_string()));
21187    cx.assert_editor_state(
21188        &r#"AAAAˇone
21189        two
21190
21191        three
21192        fourAAAAˇ
21193        five
21194
21195        siAAAAˇx"#
21196            .unindent(),
21197    );
21198}
21199
21200#[gpui::test]
21201async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
21202    init_test(cx, |_| {});
21203
21204    let mut cx = EditorTestContext::new(cx).await;
21205    cx.set_state(
21206        r#"let foo = 1;
21207let foo = 2;
21208let foo = 3;
21209let fooˇ = 4;
21210let foo = 5;
21211let foo = 6;
21212let foo = 7;
21213let foo = 8;
21214let foo = 9;
21215let foo = 10;
21216let foo = 11;
21217let foo = 12;
21218let foo = 13;
21219let foo = 14;
21220let foo = 15;"#,
21221    );
21222
21223    cx.update_editor(|e, window, cx| {
21224        assert_eq!(
21225            e.next_scroll_position,
21226            NextScrollCursorCenterTopBottom::Center,
21227            "Default next scroll direction is center",
21228        );
21229
21230        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21231        assert_eq!(
21232            e.next_scroll_position,
21233            NextScrollCursorCenterTopBottom::Top,
21234            "After center, next scroll direction should be top",
21235        );
21236
21237        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21238        assert_eq!(
21239            e.next_scroll_position,
21240            NextScrollCursorCenterTopBottom::Bottom,
21241            "After top, next scroll direction should be bottom",
21242        );
21243
21244        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21245        assert_eq!(
21246            e.next_scroll_position,
21247            NextScrollCursorCenterTopBottom::Center,
21248            "After bottom, scrolling should start over",
21249        );
21250
21251        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21252        assert_eq!(
21253            e.next_scroll_position,
21254            NextScrollCursorCenterTopBottom::Top,
21255            "Scrolling continues if retriggered fast enough"
21256        );
21257    });
21258
21259    cx.executor()
21260        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
21261    cx.executor().run_until_parked();
21262    cx.update_editor(|e, _, _| {
21263        assert_eq!(
21264            e.next_scroll_position,
21265            NextScrollCursorCenterTopBottom::Center,
21266            "If scrolling is not triggered fast enough, it should reset"
21267        );
21268    });
21269}
21270
21271#[gpui::test]
21272async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
21273    init_test(cx, |_| {});
21274    let mut cx = EditorLspTestContext::new_rust(
21275        lsp::ServerCapabilities {
21276            definition_provider: Some(lsp::OneOf::Left(true)),
21277            references_provider: Some(lsp::OneOf::Left(true)),
21278            ..lsp::ServerCapabilities::default()
21279        },
21280        cx,
21281    )
21282    .await;
21283
21284    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21285        let go_to_definition = cx
21286            .lsp
21287            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21288                move |params, _| async move {
21289                    if empty_go_to_definition {
21290                        Ok(None)
21291                    } else {
21292                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21293                            uri: params.text_document_position_params.text_document.uri,
21294                            range: lsp::Range::new(
21295                                lsp::Position::new(4, 3),
21296                                lsp::Position::new(4, 6),
21297                            ),
21298                        })))
21299                    }
21300                },
21301            );
21302        let references = cx
21303            .lsp
21304            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21305                Ok(Some(vec![lsp::Location {
21306                    uri: params.text_document_position.text_document.uri,
21307                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21308                }]))
21309            });
21310        (go_to_definition, references)
21311    };
21312
21313    cx.set_state(
21314        &r#"fn one() {
21315            let mut a = ˇtwo();
21316        }
21317
21318        fn two() {}"#
21319            .unindent(),
21320    );
21321    set_up_lsp_handlers(false, &mut cx);
21322    let navigated = cx
21323        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21324        .await
21325        .expect("Failed to navigate to definition");
21326    assert_eq!(
21327        navigated,
21328        Navigated::Yes,
21329        "Should have navigated to definition from the GetDefinition response"
21330    );
21331    cx.assert_editor_state(
21332        &r#"fn one() {
21333            let mut a = two();
21334        }
21335
21336        fn «twoˇ»() {}"#
21337            .unindent(),
21338    );
21339
21340    let editors = cx.update_workspace(|workspace, _, cx| {
21341        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21342    });
21343    cx.update_editor(|_, _, test_editor_cx| {
21344        assert_eq!(
21345            editors.len(),
21346            1,
21347            "Initially, only one, test, editor should be open in the workspace"
21348        );
21349        assert_eq!(
21350            test_editor_cx.entity(),
21351            editors.last().expect("Asserted len is 1").clone()
21352        );
21353    });
21354
21355    set_up_lsp_handlers(true, &mut cx);
21356    let navigated = cx
21357        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21358        .await
21359        .expect("Failed to navigate to lookup references");
21360    assert_eq!(
21361        navigated,
21362        Navigated::Yes,
21363        "Should have navigated to references as a fallback after empty GoToDefinition response"
21364    );
21365    // We should not change the selections in the existing file,
21366    // if opening another milti buffer with the references
21367    cx.assert_editor_state(
21368        &r#"fn one() {
21369            let mut a = two();
21370        }
21371
21372        fn «twoˇ»() {}"#
21373            .unindent(),
21374    );
21375    let editors = cx.update_workspace(|workspace, _, cx| {
21376        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21377    });
21378    cx.update_editor(|_, _, test_editor_cx| {
21379        assert_eq!(
21380            editors.len(),
21381            2,
21382            "After falling back to references search, we open a new editor with the results"
21383        );
21384        let references_fallback_text = editors
21385            .into_iter()
21386            .find(|new_editor| *new_editor != test_editor_cx.entity())
21387            .expect("Should have one non-test editor now")
21388            .read(test_editor_cx)
21389            .text(test_editor_cx);
21390        assert_eq!(
21391            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21392            "Should use the range from the references response and not the GoToDefinition one"
21393        );
21394    });
21395}
21396
21397#[gpui::test]
21398async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21399    init_test(cx, |_| {});
21400    cx.update(|cx| {
21401        let mut editor_settings = EditorSettings::get_global(cx).clone();
21402        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21403        EditorSettings::override_global(editor_settings, cx);
21404    });
21405    let mut cx = EditorLspTestContext::new_rust(
21406        lsp::ServerCapabilities {
21407            definition_provider: Some(lsp::OneOf::Left(true)),
21408            references_provider: Some(lsp::OneOf::Left(true)),
21409            ..lsp::ServerCapabilities::default()
21410        },
21411        cx,
21412    )
21413    .await;
21414    let original_state = r#"fn one() {
21415        let mut a = ˇtwo();
21416    }
21417
21418    fn two() {}"#
21419        .unindent();
21420    cx.set_state(&original_state);
21421
21422    let mut go_to_definition = cx
21423        .lsp
21424        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21425            move |_, _| async move { Ok(None) },
21426        );
21427    let _references = cx
21428        .lsp
21429        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21430            panic!("Should not call for references with no go to definition fallback")
21431        });
21432
21433    let navigated = cx
21434        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21435        .await
21436        .expect("Failed to navigate to lookup references");
21437    go_to_definition
21438        .next()
21439        .await
21440        .expect("Should have called the go_to_definition handler");
21441
21442    assert_eq!(
21443        navigated,
21444        Navigated::No,
21445        "Should have navigated to references as a fallback after empty GoToDefinition response"
21446    );
21447    cx.assert_editor_state(&original_state);
21448    let editors = cx.update_workspace(|workspace, _, cx| {
21449        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21450    });
21451    cx.update_editor(|_, _, _| {
21452        assert_eq!(
21453            editors.len(),
21454            1,
21455            "After unsuccessful fallback, no other editor should have been opened"
21456        );
21457    });
21458}
21459
21460#[gpui::test]
21461async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
21462    init_test(cx, |_| {});
21463    let mut cx = EditorLspTestContext::new_rust(
21464        lsp::ServerCapabilities {
21465            references_provider: Some(lsp::OneOf::Left(true)),
21466            ..lsp::ServerCapabilities::default()
21467        },
21468        cx,
21469    )
21470    .await;
21471
21472    cx.set_state(
21473        &r#"
21474        fn one() {
21475            let mut a = two();
21476        }
21477
21478        fn ˇtwo() {}"#
21479            .unindent(),
21480    );
21481    cx.lsp
21482        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21483            Ok(Some(vec![
21484                lsp::Location {
21485                    uri: params.text_document_position.text_document.uri.clone(),
21486                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21487                },
21488                lsp::Location {
21489                    uri: params.text_document_position.text_document.uri,
21490                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
21491                },
21492            ]))
21493        });
21494    let navigated = cx
21495        .update_editor(|editor, window, cx| {
21496            editor.find_all_references(&FindAllReferences, window, cx)
21497        })
21498        .unwrap()
21499        .await
21500        .expect("Failed to navigate to references");
21501    assert_eq!(
21502        navigated,
21503        Navigated::Yes,
21504        "Should have navigated to references from the FindAllReferences response"
21505    );
21506    cx.assert_editor_state(
21507        &r#"fn one() {
21508            let mut a = two();
21509        }
21510
21511        fn ˇtwo() {}"#
21512            .unindent(),
21513    );
21514
21515    let editors = cx.update_workspace(|workspace, _, cx| {
21516        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21517    });
21518    cx.update_editor(|_, _, _| {
21519        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
21520    });
21521
21522    cx.set_state(
21523        &r#"fn one() {
21524            let mut a = ˇtwo();
21525        }
21526
21527        fn two() {}"#
21528            .unindent(),
21529    );
21530    let navigated = cx
21531        .update_editor(|editor, window, cx| {
21532            editor.find_all_references(&FindAllReferences, window, cx)
21533        })
21534        .unwrap()
21535        .await
21536        .expect("Failed to navigate to references");
21537    assert_eq!(
21538        navigated,
21539        Navigated::Yes,
21540        "Should have navigated to references from the FindAllReferences response"
21541    );
21542    cx.assert_editor_state(
21543        &r#"fn one() {
21544            let mut a = ˇtwo();
21545        }
21546
21547        fn two() {}"#
21548            .unindent(),
21549    );
21550    let editors = cx.update_workspace(|workspace, _, cx| {
21551        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21552    });
21553    cx.update_editor(|_, _, _| {
21554        assert_eq!(
21555            editors.len(),
21556            2,
21557            "should have re-used the previous multibuffer"
21558        );
21559    });
21560
21561    cx.set_state(
21562        &r#"fn one() {
21563            let mut a = ˇtwo();
21564        }
21565        fn three() {}
21566        fn two() {}"#
21567            .unindent(),
21568    );
21569    cx.lsp
21570        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21571            Ok(Some(vec![
21572                lsp::Location {
21573                    uri: params.text_document_position.text_document.uri.clone(),
21574                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21575                },
21576                lsp::Location {
21577                    uri: params.text_document_position.text_document.uri,
21578                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
21579                },
21580            ]))
21581        });
21582    let navigated = cx
21583        .update_editor(|editor, window, cx| {
21584            editor.find_all_references(&FindAllReferences, window, cx)
21585        })
21586        .unwrap()
21587        .await
21588        .expect("Failed to navigate to references");
21589    assert_eq!(
21590        navigated,
21591        Navigated::Yes,
21592        "Should have navigated to references from the FindAllReferences response"
21593    );
21594    cx.assert_editor_state(
21595        &r#"fn one() {
21596                let mut a = ˇtwo();
21597            }
21598            fn three() {}
21599            fn two() {}"#
21600            .unindent(),
21601    );
21602    let editors = cx.update_workspace(|workspace, _, cx| {
21603        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21604    });
21605    cx.update_editor(|_, _, _| {
21606        assert_eq!(
21607            editors.len(),
21608            3,
21609            "should have used a new multibuffer as offsets changed"
21610        );
21611    });
21612}
21613#[gpui::test]
21614async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
21615    init_test(cx, |_| {});
21616
21617    let language = Arc::new(Language::new(
21618        LanguageConfig::default(),
21619        Some(tree_sitter_rust::LANGUAGE.into()),
21620    ));
21621
21622    let text = r#"
21623        #[cfg(test)]
21624        mod tests() {
21625            #[test]
21626            fn runnable_1() {
21627                let a = 1;
21628            }
21629
21630            #[test]
21631            fn runnable_2() {
21632                let a = 1;
21633                let b = 2;
21634            }
21635        }
21636    "#
21637    .unindent();
21638
21639    let fs = FakeFs::new(cx.executor());
21640    fs.insert_file("/file.rs", Default::default()).await;
21641
21642    let project = Project::test(fs, ["/a".as_ref()], cx).await;
21643    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21644    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21645    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
21646    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
21647
21648    let editor = cx.new_window_entity(|window, cx| {
21649        Editor::new(
21650            EditorMode::full(),
21651            multi_buffer,
21652            Some(project.clone()),
21653            window,
21654            cx,
21655        )
21656    });
21657
21658    editor.update_in(cx, |editor, window, cx| {
21659        let snapshot = editor.buffer().read(cx).snapshot(cx);
21660        editor.tasks.insert(
21661            (buffer.read(cx).remote_id(), 3),
21662            RunnableTasks {
21663                templates: vec![],
21664                offset: snapshot.anchor_before(43),
21665                column: 0,
21666                extra_variables: HashMap::default(),
21667                context_range: BufferOffset(43)..BufferOffset(85),
21668            },
21669        );
21670        editor.tasks.insert(
21671            (buffer.read(cx).remote_id(), 8),
21672            RunnableTasks {
21673                templates: vec![],
21674                offset: snapshot.anchor_before(86),
21675                column: 0,
21676                extra_variables: HashMap::default(),
21677                context_range: BufferOffset(86)..BufferOffset(191),
21678            },
21679        );
21680
21681        // Test finding task when cursor is inside function body
21682        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21683            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
21684        });
21685        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21686        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
21687
21688        // Test finding task when cursor is on function name
21689        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21690            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
21691        });
21692        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21693        assert_eq!(row, 8, "Should find task when cursor is on function name");
21694    });
21695}
21696
21697#[gpui::test]
21698async fn test_folding_buffers(cx: &mut TestAppContext) {
21699    init_test(cx, |_| {});
21700
21701    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21702    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
21703    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
21704
21705    let fs = FakeFs::new(cx.executor());
21706    fs.insert_tree(
21707        path!("/a"),
21708        json!({
21709            "first.rs": sample_text_1,
21710            "second.rs": sample_text_2,
21711            "third.rs": sample_text_3,
21712        }),
21713    )
21714    .await;
21715    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21716    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21717    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21718    let worktree = project.update(cx, |project, cx| {
21719        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21720        assert_eq!(worktrees.len(), 1);
21721        worktrees.pop().unwrap()
21722    });
21723    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21724
21725    let buffer_1 = project
21726        .update(cx, |project, cx| {
21727            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
21728        })
21729        .await
21730        .unwrap();
21731    let buffer_2 = project
21732        .update(cx, |project, cx| {
21733            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
21734        })
21735        .await
21736        .unwrap();
21737    let buffer_3 = project
21738        .update(cx, |project, cx| {
21739            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
21740        })
21741        .await
21742        .unwrap();
21743
21744    let multi_buffer = cx.new(|cx| {
21745        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21746        multi_buffer.push_excerpts(
21747            buffer_1.clone(),
21748            [
21749                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21750                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21751                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21752            ],
21753            cx,
21754        );
21755        multi_buffer.push_excerpts(
21756            buffer_2.clone(),
21757            [
21758                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21759                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21760                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21761            ],
21762            cx,
21763        );
21764        multi_buffer.push_excerpts(
21765            buffer_3.clone(),
21766            [
21767                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21768                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21769                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21770            ],
21771            cx,
21772        );
21773        multi_buffer
21774    });
21775    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21776        Editor::new(
21777            EditorMode::full(),
21778            multi_buffer.clone(),
21779            Some(project.clone()),
21780            window,
21781            cx,
21782        )
21783    });
21784
21785    assert_eq!(
21786        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21787        "\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",
21788    );
21789
21790    multi_buffer_editor.update(cx, |editor, cx| {
21791        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21792    });
21793    assert_eq!(
21794        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21795        "\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",
21796        "After folding the first buffer, its text should not be displayed"
21797    );
21798
21799    multi_buffer_editor.update(cx, |editor, cx| {
21800        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21801    });
21802    assert_eq!(
21803        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21804        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
21805        "After folding the second buffer, its text should not be displayed"
21806    );
21807
21808    multi_buffer_editor.update(cx, |editor, cx| {
21809        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21810    });
21811    assert_eq!(
21812        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21813        "\n\n\n\n\n",
21814        "After folding the third buffer, its text should not be displayed"
21815    );
21816
21817    // Emulate selection inside the fold logic, that should work
21818    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21819        editor
21820            .snapshot(window, cx)
21821            .next_line_boundary(Point::new(0, 4));
21822    });
21823
21824    multi_buffer_editor.update(cx, |editor, cx| {
21825        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21826    });
21827    assert_eq!(
21828        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21829        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21830        "After unfolding the second buffer, its text should be displayed"
21831    );
21832
21833    // Typing inside of buffer 1 causes that buffer to be unfolded.
21834    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21835        assert_eq!(
21836            multi_buffer
21837                .read(cx)
21838                .snapshot(cx)
21839                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
21840                .collect::<String>(),
21841            "bbbb"
21842        );
21843        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21844            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
21845        });
21846        editor.handle_input("B", window, cx);
21847    });
21848
21849    assert_eq!(
21850        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21851        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21852        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
21853    );
21854
21855    multi_buffer_editor.update(cx, |editor, cx| {
21856        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21857    });
21858    assert_eq!(
21859        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21860        "\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",
21861        "After unfolding the all buffers, all original text should be displayed"
21862    );
21863}
21864
21865#[gpui::test]
21866async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
21867    init_test(cx, |_| {});
21868
21869    let sample_text_1 = "1111\n2222\n3333".to_string();
21870    let sample_text_2 = "4444\n5555\n6666".to_string();
21871    let sample_text_3 = "7777\n8888\n9999".to_string();
21872
21873    let fs = FakeFs::new(cx.executor());
21874    fs.insert_tree(
21875        path!("/a"),
21876        json!({
21877            "first.rs": sample_text_1,
21878            "second.rs": sample_text_2,
21879            "third.rs": sample_text_3,
21880        }),
21881    )
21882    .await;
21883    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21884    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21885    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21886    let worktree = project.update(cx, |project, cx| {
21887        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21888        assert_eq!(worktrees.len(), 1);
21889        worktrees.pop().unwrap()
21890    });
21891    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21892
21893    let buffer_1 = project
21894        .update(cx, |project, cx| {
21895            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
21896        })
21897        .await
21898        .unwrap();
21899    let buffer_2 = project
21900        .update(cx, |project, cx| {
21901            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
21902        })
21903        .await
21904        .unwrap();
21905    let buffer_3 = project
21906        .update(cx, |project, cx| {
21907            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
21908        })
21909        .await
21910        .unwrap();
21911
21912    let multi_buffer = cx.new(|cx| {
21913        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21914        multi_buffer.push_excerpts(
21915            buffer_1.clone(),
21916            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21917            cx,
21918        );
21919        multi_buffer.push_excerpts(
21920            buffer_2.clone(),
21921            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21922            cx,
21923        );
21924        multi_buffer.push_excerpts(
21925            buffer_3.clone(),
21926            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21927            cx,
21928        );
21929        multi_buffer
21930    });
21931
21932    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21933        Editor::new(
21934            EditorMode::full(),
21935            multi_buffer,
21936            Some(project.clone()),
21937            window,
21938            cx,
21939        )
21940    });
21941
21942    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
21943    assert_eq!(
21944        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21945        full_text,
21946    );
21947
21948    multi_buffer_editor.update(cx, |editor, cx| {
21949        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21950    });
21951    assert_eq!(
21952        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21953        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
21954        "After folding the first buffer, its text should not be displayed"
21955    );
21956
21957    multi_buffer_editor.update(cx, |editor, cx| {
21958        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21959    });
21960
21961    assert_eq!(
21962        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21963        "\n\n\n\n\n\n7777\n8888\n9999",
21964        "After folding the second buffer, its text should not be displayed"
21965    );
21966
21967    multi_buffer_editor.update(cx, |editor, cx| {
21968        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21969    });
21970    assert_eq!(
21971        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21972        "\n\n\n\n\n",
21973        "After folding the third buffer, its text should not be displayed"
21974    );
21975
21976    multi_buffer_editor.update(cx, |editor, cx| {
21977        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21978    });
21979    assert_eq!(
21980        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21981        "\n\n\n\n4444\n5555\n6666\n\n",
21982        "After unfolding the second buffer, its text should be displayed"
21983    );
21984
21985    multi_buffer_editor.update(cx, |editor, cx| {
21986        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
21987    });
21988    assert_eq!(
21989        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21990        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
21991        "After unfolding the first buffer, its text should be displayed"
21992    );
21993
21994    multi_buffer_editor.update(cx, |editor, cx| {
21995        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21996    });
21997    assert_eq!(
21998        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21999        full_text,
22000        "After unfolding all buffers, all original text should be displayed"
22001    );
22002}
22003
22004#[gpui::test]
22005async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
22006    init_test(cx, |_| {});
22007
22008    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22009
22010    let fs = FakeFs::new(cx.executor());
22011    fs.insert_tree(
22012        path!("/a"),
22013        json!({
22014            "main.rs": sample_text,
22015        }),
22016    )
22017    .await;
22018    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22019    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22020    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22021    let worktree = project.update(cx, |project, cx| {
22022        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22023        assert_eq!(worktrees.len(), 1);
22024        worktrees.pop().unwrap()
22025    });
22026    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22027
22028    let buffer_1 = project
22029        .update(cx, |project, cx| {
22030            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22031        })
22032        .await
22033        .unwrap();
22034
22035    let multi_buffer = cx.new(|cx| {
22036        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22037        multi_buffer.push_excerpts(
22038            buffer_1.clone(),
22039            [ExcerptRange::new(
22040                Point::new(0, 0)
22041                    ..Point::new(
22042                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
22043                        0,
22044                    ),
22045            )],
22046            cx,
22047        );
22048        multi_buffer
22049    });
22050    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22051        Editor::new(
22052            EditorMode::full(),
22053            multi_buffer,
22054            Some(project.clone()),
22055            window,
22056            cx,
22057        )
22058    });
22059
22060    let selection_range = Point::new(1, 0)..Point::new(2, 0);
22061    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22062        enum TestHighlight {}
22063        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22064        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
22065        editor.highlight_text::<TestHighlight>(
22066            vec![highlight_range.clone()],
22067            HighlightStyle::color(Hsla::green()),
22068            cx,
22069        );
22070        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22071            s.select_ranges(Some(highlight_range))
22072        });
22073    });
22074
22075    let full_text = format!("\n\n{sample_text}");
22076    assert_eq!(
22077        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22078        full_text,
22079    );
22080}
22081
22082#[gpui::test]
22083async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22084    init_test(cx, |_| {});
22085    cx.update(|cx| {
22086        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22087            "keymaps/default-linux.json",
22088            cx,
22089        )
22090        .unwrap();
22091        cx.bind_keys(default_key_bindings);
22092    });
22093
22094    let (editor, cx) = cx.add_window_view(|window, cx| {
22095        let multi_buffer = MultiBuffer::build_multi(
22096            [
22097                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22098                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22099                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22100                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22101            ],
22102            cx,
22103        );
22104        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22105
22106        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22107        // fold all but the second buffer, so that we test navigating between two
22108        // adjacent folded buffers, as well as folded buffers at the start and
22109        // end the multibuffer
22110        editor.fold_buffer(buffer_ids[0], cx);
22111        editor.fold_buffer(buffer_ids[2], cx);
22112        editor.fold_buffer(buffer_ids[3], cx);
22113
22114        editor
22115    });
22116    cx.simulate_resize(size(px(1000.), px(1000.)));
22117
22118    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22119    cx.assert_excerpts_with_selections(indoc! {"
22120        [EXCERPT]
22121        ˇ[FOLDED]
22122        [EXCERPT]
22123        a1
22124        b1
22125        [EXCERPT]
22126        [FOLDED]
22127        [EXCERPT]
22128        [FOLDED]
22129        "
22130    });
22131    cx.simulate_keystroke("down");
22132    cx.assert_excerpts_with_selections(indoc! {"
22133        [EXCERPT]
22134        [FOLDED]
22135        [EXCERPT]
22136        ˇa1
22137        b1
22138        [EXCERPT]
22139        [FOLDED]
22140        [EXCERPT]
22141        [FOLDED]
22142        "
22143    });
22144    cx.simulate_keystroke("down");
22145    cx.assert_excerpts_with_selections(indoc! {"
22146        [EXCERPT]
22147        [FOLDED]
22148        [EXCERPT]
22149        a1
22150        ˇb1
22151        [EXCERPT]
22152        [FOLDED]
22153        [EXCERPT]
22154        [FOLDED]
22155        "
22156    });
22157    cx.simulate_keystroke("down");
22158    cx.assert_excerpts_with_selections(indoc! {"
22159        [EXCERPT]
22160        [FOLDED]
22161        [EXCERPT]
22162        a1
22163        b1
22164        ˇ[EXCERPT]
22165        [FOLDED]
22166        [EXCERPT]
22167        [FOLDED]
22168        "
22169    });
22170    cx.simulate_keystroke("down");
22171    cx.assert_excerpts_with_selections(indoc! {"
22172        [EXCERPT]
22173        [FOLDED]
22174        [EXCERPT]
22175        a1
22176        b1
22177        [EXCERPT]
22178        ˇ[FOLDED]
22179        [EXCERPT]
22180        [FOLDED]
22181        "
22182    });
22183    for _ in 0..5 {
22184        cx.simulate_keystroke("down");
22185        cx.assert_excerpts_with_selections(indoc! {"
22186            [EXCERPT]
22187            [FOLDED]
22188            [EXCERPT]
22189            a1
22190            b1
22191            [EXCERPT]
22192            [FOLDED]
22193            [EXCERPT]
22194            ˇ[FOLDED]
22195            "
22196        });
22197    }
22198
22199    cx.simulate_keystroke("up");
22200    cx.assert_excerpts_with_selections(indoc! {"
22201        [EXCERPT]
22202        [FOLDED]
22203        [EXCERPT]
22204        a1
22205        b1
22206        [EXCERPT]
22207        ˇ[FOLDED]
22208        [EXCERPT]
22209        [FOLDED]
22210        "
22211    });
22212    cx.simulate_keystroke("up");
22213    cx.assert_excerpts_with_selections(indoc! {"
22214        [EXCERPT]
22215        [FOLDED]
22216        [EXCERPT]
22217        a1
22218        b1
22219        ˇ[EXCERPT]
22220        [FOLDED]
22221        [EXCERPT]
22222        [FOLDED]
22223        "
22224    });
22225    cx.simulate_keystroke("up");
22226    cx.assert_excerpts_with_selections(indoc! {"
22227        [EXCERPT]
22228        [FOLDED]
22229        [EXCERPT]
22230        a1
22231        ˇb1
22232        [EXCERPT]
22233        [FOLDED]
22234        [EXCERPT]
22235        [FOLDED]
22236        "
22237    });
22238    cx.simulate_keystroke("up");
22239    cx.assert_excerpts_with_selections(indoc! {"
22240        [EXCERPT]
22241        [FOLDED]
22242        [EXCERPT]
22243        ˇa1
22244        b1
22245        [EXCERPT]
22246        [FOLDED]
22247        [EXCERPT]
22248        [FOLDED]
22249        "
22250    });
22251    for _ in 0..5 {
22252        cx.simulate_keystroke("up");
22253        cx.assert_excerpts_with_selections(indoc! {"
22254            [EXCERPT]
22255            ˇ[FOLDED]
22256            [EXCERPT]
22257            a1
22258            b1
22259            [EXCERPT]
22260            [FOLDED]
22261            [EXCERPT]
22262            [FOLDED]
22263            "
22264        });
22265    }
22266}
22267
22268#[gpui::test]
22269async fn test_edit_prediction_text(cx: &mut TestAppContext) {
22270    init_test(cx, |_| {});
22271
22272    // Simple insertion
22273    assert_highlighted_edits(
22274        "Hello, world!",
22275        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
22276        true,
22277        cx,
22278        |highlighted_edits, cx| {
22279            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
22280            assert_eq!(highlighted_edits.highlights.len(), 1);
22281            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
22282            assert_eq!(
22283                highlighted_edits.highlights[0].1.background_color,
22284                Some(cx.theme().status().created_background)
22285            );
22286        },
22287    )
22288    .await;
22289
22290    // Replacement
22291    assert_highlighted_edits(
22292        "This is a test.",
22293        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
22294        false,
22295        cx,
22296        |highlighted_edits, cx| {
22297            assert_eq!(highlighted_edits.text, "That is a test.");
22298            assert_eq!(highlighted_edits.highlights.len(), 1);
22299            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
22300            assert_eq!(
22301                highlighted_edits.highlights[0].1.background_color,
22302                Some(cx.theme().status().created_background)
22303            );
22304        },
22305    )
22306    .await;
22307
22308    // Multiple edits
22309    assert_highlighted_edits(
22310        "Hello, world!",
22311        vec![
22312            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
22313            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
22314        ],
22315        false,
22316        cx,
22317        |highlighted_edits, cx| {
22318            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
22319            assert_eq!(highlighted_edits.highlights.len(), 2);
22320            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
22321            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
22322            assert_eq!(
22323                highlighted_edits.highlights[0].1.background_color,
22324                Some(cx.theme().status().created_background)
22325            );
22326            assert_eq!(
22327                highlighted_edits.highlights[1].1.background_color,
22328                Some(cx.theme().status().created_background)
22329            );
22330        },
22331    )
22332    .await;
22333
22334    // Multiple lines with edits
22335    assert_highlighted_edits(
22336        "First line\nSecond line\nThird line\nFourth line",
22337        vec![
22338            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
22339            (
22340                Point::new(2, 0)..Point::new(2, 10),
22341                "New third line".to_string(),
22342            ),
22343            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
22344        ],
22345        false,
22346        cx,
22347        |highlighted_edits, cx| {
22348            assert_eq!(
22349                highlighted_edits.text,
22350                "Second modified\nNew third line\nFourth updated line"
22351            );
22352            assert_eq!(highlighted_edits.highlights.len(), 3);
22353            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
22354            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
22355            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
22356            for highlight in &highlighted_edits.highlights {
22357                assert_eq!(
22358                    highlight.1.background_color,
22359                    Some(cx.theme().status().created_background)
22360                );
22361            }
22362        },
22363    )
22364    .await;
22365}
22366
22367#[gpui::test]
22368async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
22369    init_test(cx, |_| {});
22370
22371    // Deletion
22372    assert_highlighted_edits(
22373        "Hello, world!",
22374        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
22375        true,
22376        cx,
22377        |highlighted_edits, cx| {
22378            assert_eq!(highlighted_edits.text, "Hello, world!");
22379            assert_eq!(highlighted_edits.highlights.len(), 1);
22380            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
22381            assert_eq!(
22382                highlighted_edits.highlights[0].1.background_color,
22383                Some(cx.theme().status().deleted_background)
22384            );
22385        },
22386    )
22387    .await;
22388
22389    // Insertion
22390    assert_highlighted_edits(
22391        "Hello, world!",
22392        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
22393        true,
22394        cx,
22395        |highlighted_edits, cx| {
22396            assert_eq!(highlighted_edits.highlights.len(), 1);
22397            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
22398            assert_eq!(
22399                highlighted_edits.highlights[0].1.background_color,
22400                Some(cx.theme().status().created_background)
22401            );
22402        },
22403    )
22404    .await;
22405}
22406
22407async fn assert_highlighted_edits(
22408    text: &str,
22409    edits: Vec<(Range<Point>, String)>,
22410    include_deletions: bool,
22411    cx: &mut TestAppContext,
22412    assertion_fn: impl Fn(HighlightedText, &App),
22413) {
22414    let window = cx.add_window(|window, cx| {
22415        let buffer = MultiBuffer::build_simple(text, cx);
22416        Editor::new(EditorMode::full(), buffer, None, window, cx)
22417    });
22418    let cx = &mut VisualTestContext::from_window(*window, cx);
22419
22420    let (buffer, snapshot) = window
22421        .update(cx, |editor, _window, cx| {
22422            (
22423                editor.buffer().clone(),
22424                editor.buffer().read(cx).snapshot(cx),
22425            )
22426        })
22427        .unwrap();
22428
22429    let edits = edits
22430        .into_iter()
22431        .map(|(range, edit)| {
22432            (
22433                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
22434                edit,
22435            )
22436        })
22437        .collect::<Vec<_>>();
22438
22439    let text_anchor_edits = edits
22440        .clone()
22441        .into_iter()
22442        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
22443        .collect::<Vec<_>>();
22444
22445    let edit_preview = window
22446        .update(cx, |_, _window, cx| {
22447            buffer
22448                .read(cx)
22449                .as_singleton()
22450                .unwrap()
22451                .read(cx)
22452                .preview_edits(text_anchor_edits.into(), cx)
22453        })
22454        .unwrap()
22455        .await;
22456
22457    cx.update(|_window, cx| {
22458        let highlighted_edits = edit_prediction_edit_text(
22459            snapshot.as_singleton().unwrap().2,
22460            &edits,
22461            &edit_preview,
22462            include_deletions,
22463            cx,
22464        );
22465        assertion_fn(highlighted_edits, cx)
22466    });
22467}
22468
22469#[track_caller]
22470fn assert_breakpoint(
22471    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
22472    path: &Arc<Path>,
22473    expected: Vec<(u32, Breakpoint)>,
22474) {
22475    if expected.is_empty() {
22476        assert!(!breakpoints.contains_key(path), "{}", path.display());
22477    } else {
22478        let mut breakpoint = breakpoints
22479            .get(path)
22480            .unwrap()
22481            .iter()
22482            .map(|breakpoint| {
22483                (
22484                    breakpoint.row,
22485                    Breakpoint {
22486                        message: breakpoint.message.clone(),
22487                        state: breakpoint.state,
22488                        condition: breakpoint.condition.clone(),
22489                        hit_condition: breakpoint.hit_condition.clone(),
22490                    },
22491                )
22492            })
22493            .collect::<Vec<_>>();
22494
22495        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
22496
22497        assert_eq!(expected, breakpoint);
22498    }
22499}
22500
22501fn add_log_breakpoint_at_cursor(
22502    editor: &mut Editor,
22503    log_message: &str,
22504    window: &mut Window,
22505    cx: &mut Context<Editor>,
22506) {
22507    let (anchor, bp) = editor
22508        .breakpoints_at_cursors(window, cx)
22509        .first()
22510        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
22511        .unwrap_or_else(|| {
22512            let cursor_position: Point = editor.selections.newest(cx).head();
22513
22514            let breakpoint_position = editor
22515                .snapshot(window, cx)
22516                .display_snapshot
22517                .buffer_snapshot
22518                .anchor_before(Point::new(cursor_position.row, 0));
22519
22520            (breakpoint_position, Breakpoint::new_log(log_message))
22521        });
22522
22523    editor.edit_breakpoint_at_anchor(
22524        anchor,
22525        bp,
22526        BreakpointEditAction::EditLogMessage(log_message.into()),
22527        cx,
22528    );
22529}
22530
22531#[gpui::test]
22532async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
22533    init_test(cx, |_| {});
22534
22535    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22536    let fs = FakeFs::new(cx.executor());
22537    fs.insert_tree(
22538        path!("/a"),
22539        json!({
22540            "main.rs": sample_text,
22541        }),
22542    )
22543    .await;
22544    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22545    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22546    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22547
22548    let fs = FakeFs::new(cx.executor());
22549    fs.insert_tree(
22550        path!("/a"),
22551        json!({
22552            "main.rs": sample_text,
22553        }),
22554    )
22555    .await;
22556    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22557    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22558    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22559    let worktree_id = workspace
22560        .update(cx, |workspace, _window, cx| {
22561            workspace.project().update(cx, |project, cx| {
22562                project.worktrees(cx).next().unwrap().read(cx).id()
22563            })
22564        })
22565        .unwrap();
22566
22567    let buffer = project
22568        .update(cx, |project, cx| {
22569            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22570        })
22571        .await
22572        .unwrap();
22573
22574    let (editor, cx) = cx.add_window_view(|window, cx| {
22575        Editor::new(
22576            EditorMode::full(),
22577            MultiBuffer::build_from_buffer(buffer, cx),
22578            Some(project.clone()),
22579            window,
22580            cx,
22581        )
22582    });
22583
22584    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22585    let abs_path = project.read_with(cx, |project, cx| {
22586        project
22587            .absolute_path(&project_path, cx)
22588            .map(Arc::from)
22589            .unwrap()
22590    });
22591
22592    // assert we can add breakpoint on the first line
22593    editor.update_in(cx, |editor, window, cx| {
22594        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22595        editor.move_to_end(&MoveToEnd, window, cx);
22596        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22597    });
22598
22599    let breakpoints = editor.update(cx, |editor, cx| {
22600        editor
22601            .breakpoint_store()
22602            .as_ref()
22603            .unwrap()
22604            .read(cx)
22605            .all_source_breakpoints(cx)
22606    });
22607
22608    assert_eq!(1, breakpoints.len());
22609    assert_breakpoint(
22610        &breakpoints,
22611        &abs_path,
22612        vec![
22613            (0, Breakpoint::new_standard()),
22614            (3, Breakpoint::new_standard()),
22615        ],
22616    );
22617
22618    editor.update_in(cx, |editor, window, cx| {
22619        editor.move_to_beginning(&MoveToBeginning, window, cx);
22620        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22621    });
22622
22623    let breakpoints = editor.update(cx, |editor, cx| {
22624        editor
22625            .breakpoint_store()
22626            .as_ref()
22627            .unwrap()
22628            .read(cx)
22629            .all_source_breakpoints(cx)
22630    });
22631
22632    assert_eq!(1, breakpoints.len());
22633    assert_breakpoint(
22634        &breakpoints,
22635        &abs_path,
22636        vec![(3, Breakpoint::new_standard())],
22637    );
22638
22639    editor.update_in(cx, |editor, window, cx| {
22640        editor.move_to_end(&MoveToEnd, window, cx);
22641        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22642    });
22643
22644    let breakpoints = editor.update(cx, |editor, cx| {
22645        editor
22646            .breakpoint_store()
22647            .as_ref()
22648            .unwrap()
22649            .read(cx)
22650            .all_source_breakpoints(cx)
22651    });
22652
22653    assert_eq!(0, breakpoints.len());
22654    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22655}
22656
22657#[gpui::test]
22658async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
22659    init_test(cx, |_| {});
22660
22661    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22662
22663    let fs = FakeFs::new(cx.executor());
22664    fs.insert_tree(
22665        path!("/a"),
22666        json!({
22667            "main.rs": sample_text,
22668        }),
22669    )
22670    .await;
22671    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22672    let (workspace, cx) =
22673        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22674
22675    let worktree_id = workspace.update(cx, |workspace, cx| {
22676        workspace.project().update(cx, |project, cx| {
22677            project.worktrees(cx).next().unwrap().read(cx).id()
22678        })
22679    });
22680
22681    let buffer = project
22682        .update(cx, |project, cx| {
22683            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22684        })
22685        .await
22686        .unwrap();
22687
22688    let (editor, cx) = cx.add_window_view(|window, cx| {
22689        Editor::new(
22690            EditorMode::full(),
22691            MultiBuffer::build_from_buffer(buffer, cx),
22692            Some(project.clone()),
22693            window,
22694            cx,
22695        )
22696    });
22697
22698    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22699    let abs_path = project.read_with(cx, |project, cx| {
22700        project
22701            .absolute_path(&project_path, cx)
22702            .map(Arc::from)
22703            .unwrap()
22704    });
22705
22706    editor.update_in(cx, |editor, window, cx| {
22707        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22708    });
22709
22710    let breakpoints = editor.update(cx, |editor, cx| {
22711        editor
22712            .breakpoint_store()
22713            .as_ref()
22714            .unwrap()
22715            .read(cx)
22716            .all_source_breakpoints(cx)
22717    });
22718
22719    assert_breakpoint(
22720        &breakpoints,
22721        &abs_path,
22722        vec![(0, Breakpoint::new_log("hello world"))],
22723    );
22724
22725    // Removing a log message from a log breakpoint should remove it
22726    editor.update_in(cx, |editor, window, cx| {
22727        add_log_breakpoint_at_cursor(editor, "", window, cx);
22728    });
22729
22730    let breakpoints = editor.update(cx, |editor, cx| {
22731        editor
22732            .breakpoint_store()
22733            .as_ref()
22734            .unwrap()
22735            .read(cx)
22736            .all_source_breakpoints(cx)
22737    });
22738
22739    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22740
22741    editor.update_in(cx, |editor, window, cx| {
22742        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22743        editor.move_to_end(&MoveToEnd, window, cx);
22744        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22745        // Not adding a log message to a standard breakpoint shouldn't remove it
22746        add_log_breakpoint_at_cursor(editor, "", window, cx);
22747    });
22748
22749    let breakpoints = editor.update(cx, |editor, cx| {
22750        editor
22751            .breakpoint_store()
22752            .as_ref()
22753            .unwrap()
22754            .read(cx)
22755            .all_source_breakpoints(cx)
22756    });
22757
22758    assert_breakpoint(
22759        &breakpoints,
22760        &abs_path,
22761        vec![
22762            (0, Breakpoint::new_standard()),
22763            (3, Breakpoint::new_standard()),
22764        ],
22765    );
22766
22767    editor.update_in(cx, |editor, window, cx| {
22768        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22769    });
22770
22771    let breakpoints = editor.update(cx, |editor, cx| {
22772        editor
22773            .breakpoint_store()
22774            .as_ref()
22775            .unwrap()
22776            .read(cx)
22777            .all_source_breakpoints(cx)
22778    });
22779
22780    assert_breakpoint(
22781        &breakpoints,
22782        &abs_path,
22783        vec![
22784            (0, Breakpoint::new_standard()),
22785            (3, Breakpoint::new_log("hello world")),
22786        ],
22787    );
22788
22789    editor.update_in(cx, |editor, window, cx| {
22790        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
22791    });
22792
22793    let breakpoints = editor.update(cx, |editor, cx| {
22794        editor
22795            .breakpoint_store()
22796            .as_ref()
22797            .unwrap()
22798            .read(cx)
22799            .all_source_breakpoints(cx)
22800    });
22801
22802    assert_breakpoint(
22803        &breakpoints,
22804        &abs_path,
22805        vec![
22806            (0, Breakpoint::new_standard()),
22807            (3, Breakpoint::new_log("hello Earth!!")),
22808        ],
22809    );
22810}
22811
22812/// This also tests that Editor::breakpoint_at_cursor_head is working properly
22813/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
22814/// or when breakpoints were placed out of order. This tests for a regression too
22815#[gpui::test]
22816async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
22817    init_test(cx, |_| {});
22818
22819    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22820    let fs = FakeFs::new(cx.executor());
22821    fs.insert_tree(
22822        path!("/a"),
22823        json!({
22824            "main.rs": sample_text,
22825        }),
22826    )
22827    .await;
22828    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22829    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22830    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22831
22832    let fs = FakeFs::new(cx.executor());
22833    fs.insert_tree(
22834        path!("/a"),
22835        json!({
22836            "main.rs": sample_text,
22837        }),
22838    )
22839    .await;
22840    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22841    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22842    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22843    let worktree_id = workspace
22844        .update(cx, |workspace, _window, cx| {
22845            workspace.project().update(cx, |project, cx| {
22846                project.worktrees(cx).next().unwrap().read(cx).id()
22847            })
22848        })
22849        .unwrap();
22850
22851    let buffer = project
22852        .update(cx, |project, cx| {
22853            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22854        })
22855        .await
22856        .unwrap();
22857
22858    let (editor, cx) = cx.add_window_view(|window, cx| {
22859        Editor::new(
22860            EditorMode::full(),
22861            MultiBuffer::build_from_buffer(buffer, cx),
22862            Some(project.clone()),
22863            window,
22864            cx,
22865        )
22866    });
22867
22868    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22869    let abs_path = project.read_with(cx, |project, cx| {
22870        project
22871            .absolute_path(&project_path, cx)
22872            .map(Arc::from)
22873            .unwrap()
22874    });
22875
22876    // assert we can add breakpoint on the first line
22877    editor.update_in(cx, |editor, window, cx| {
22878        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22879        editor.move_to_end(&MoveToEnd, window, cx);
22880        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22881        editor.move_up(&MoveUp, window, cx);
22882        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22883    });
22884
22885    let breakpoints = editor.update(cx, |editor, cx| {
22886        editor
22887            .breakpoint_store()
22888            .as_ref()
22889            .unwrap()
22890            .read(cx)
22891            .all_source_breakpoints(cx)
22892    });
22893
22894    assert_eq!(1, breakpoints.len());
22895    assert_breakpoint(
22896        &breakpoints,
22897        &abs_path,
22898        vec![
22899            (0, Breakpoint::new_standard()),
22900            (2, Breakpoint::new_standard()),
22901            (3, Breakpoint::new_standard()),
22902        ],
22903    );
22904
22905    editor.update_in(cx, |editor, window, cx| {
22906        editor.move_to_beginning(&MoveToBeginning, window, cx);
22907        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22908        editor.move_to_end(&MoveToEnd, window, cx);
22909        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22910        // Disabling a breakpoint that doesn't exist should do nothing
22911        editor.move_up(&MoveUp, window, cx);
22912        editor.move_up(&MoveUp, window, cx);
22913        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22914    });
22915
22916    let breakpoints = editor.update(cx, |editor, cx| {
22917        editor
22918            .breakpoint_store()
22919            .as_ref()
22920            .unwrap()
22921            .read(cx)
22922            .all_source_breakpoints(cx)
22923    });
22924
22925    let disable_breakpoint = {
22926        let mut bp = Breakpoint::new_standard();
22927        bp.state = BreakpointState::Disabled;
22928        bp
22929    };
22930
22931    assert_eq!(1, breakpoints.len());
22932    assert_breakpoint(
22933        &breakpoints,
22934        &abs_path,
22935        vec![
22936            (0, disable_breakpoint.clone()),
22937            (2, Breakpoint::new_standard()),
22938            (3, disable_breakpoint.clone()),
22939        ],
22940    );
22941
22942    editor.update_in(cx, |editor, window, cx| {
22943        editor.move_to_beginning(&MoveToBeginning, window, cx);
22944        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22945        editor.move_to_end(&MoveToEnd, window, cx);
22946        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22947        editor.move_up(&MoveUp, window, cx);
22948        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22949    });
22950
22951    let breakpoints = editor.update(cx, |editor, cx| {
22952        editor
22953            .breakpoint_store()
22954            .as_ref()
22955            .unwrap()
22956            .read(cx)
22957            .all_source_breakpoints(cx)
22958    });
22959
22960    assert_eq!(1, breakpoints.len());
22961    assert_breakpoint(
22962        &breakpoints,
22963        &abs_path,
22964        vec![
22965            (0, Breakpoint::new_standard()),
22966            (2, disable_breakpoint),
22967            (3, Breakpoint::new_standard()),
22968        ],
22969    );
22970}
22971
22972#[gpui::test]
22973async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
22974    init_test(cx, |_| {});
22975    let capabilities = lsp::ServerCapabilities {
22976        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
22977            prepare_provider: Some(true),
22978            work_done_progress_options: Default::default(),
22979        })),
22980        ..Default::default()
22981    };
22982    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
22983
22984    cx.set_state(indoc! {"
22985        struct Fˇoo {}
22986    "});
22987
22988    cx.update_editor(|editor, _, cx| {
22989        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
22990        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
22991        editor.highlight_background::<DocumentHighlightRead>(
22992            &[highlight_range],
22993            |theme| theme.colors().editor_document_highlight_read_background,
22994            cx,
22995        );
22996    });
22997
22998    let mut prepare_rename_handler = cx
22999        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
23000            move |_, _, _| async move {
23001                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
23002                    start: lsp::Position {
23003                        line: 0,
23004                        character: 7,
23005                    },
23006                    end: lsp::Position {
23007                        line: 0,
23008                        character: 10,
23009                    },
23010                })))
23011            },
23012        );
23013    let prepare_rename_task = cx
23014        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23015        .expect("Prepare rename was not started");
23016    prepare_rename_handler.next().await.unwrap();
23017    prepare_rename_task.await.expect("Prepare rename failed");
23018
23019    let mut rename_handler =
23020        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23021            let edit = lsp::TextEdit {
23022                range: lsp::Range {
23023                    start: lsp::Position {
23024                        line: 0,
23025                        character: 7,
23026                    },
23027                    end: lsp::Position {
23028                        line: 0,
23029                        character: 10,
23030                    },
23031                },
23032                new_text: "FooRenamed".to_string(),
23033            };
23034            Ok(Some(lsp::WorkspaceEdit::new(
23035                // Specify the same edit twice
23036                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
23037            )))
23038        });
23039    let rename_task = cx
23040        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23041        .expect("Confirm rename was not started");
23042    rename_handler.next().await.unwrap();
23043    rename_task.await.expect("Confirm rename failed");
23044    cx.run_until_parked();
23045
23046    // Despite two edits, only one is actually applied as those are identical
23047    cx.assert_editor_state(indoc! {"
23048        struct FooRenamedˇ {}
23049    "});
23050}
23051
23052#[gpui::test]
23053async fn test_rename_without_prepare(cx: &mut TestAppContext) {
23054    init_test(cx, |_| {});
23055    // These capabilities indicate that the server does not support prepare rename.
23056    let capabilities = lsp::ServerCapabilities {
23057        rename_provider: Some(lsp::OneOf::Left(true)),
23058        ..Default::default()
23059    };
23060    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23061
23062    cx.set_state(indoc! {"
23063        struct Fˇoo {}
23064    "});
23065
23066    cx.update_editor(|editor, _window, cx| {
23067        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23068        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23069        editor.highlight_background::<DocumentHighlightRead>(
23070            &[highlight_range],
23071            |theme| theme.colors().editor_document_highlight_read_background,
23072            cx,
23073        );
23074    });
23075
23076    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23077        .expect("Prepare rename was not started")
23078        .await
23079        .expect("Prepare rename failed");
23080
23081    let mut rename_handler =
23082        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23083            let edit = lsp::TextEdit {
23084                range: lsp::Range {
23085                    start: lsp::Position {
23086                        line: 0,
23087                        character: 7,
23088                    },
23089                    end: lsp::Position {
23090                        line: 0,
23091                        character: 10,
23092                    },
23093                },
23094                new_text: "FooRenamed".to_string(),
23095            };
23096            Ok(Some(lsp::WorkspaceEdit::new(
23097                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23098            )))
23099        });
23100    let rename_task = cx
23101        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23102        .expect("Confirm rename was not started");
23103    rename_handler.next().await.unwrap();
23104    rename_task.await.expect("Confirm rename failed");
23105    cx.run_until_parked();
23106
23107    // Correct range is renamed, as `surrounding_word` is used to find it.
23108    cx.assert_editor_state(indoc! {"
23109        struct FooRenamedˇ {}
23110    "});
23111}
23112
23113#[gpui::test]
23114async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23115    init_test(cx, |_| {});
23116    let mut cx = EditorTestContext::new(cx).await;
23117
23118    let language = Arc::new(
23119        Language::new(
23120            LanguageConfig::default(),
23121            Some(tree_sitter_html::LANGUAGE.into()),
23122        )
23123        .with_brackets_query(
23124            r#"
23125            ("<" @open "/>" @close)
23126            ("</" @open ">" @close)
23127            ("<" @open ">" @close)
23128            ("\"" @open "\"" @close)
23129            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23130        "#,
23131        )
23132        .unwrap(),
23133    );
23134    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23135
23136    cx.set_state(indoc! {"
23137        <span>ˇ</span>
23138    "});
23139    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23140    cx.assert_editor_state(indoc! {"
23141        <span>
23142        ˇ
23143        </span>
23144    "});
23145
23146    cx.set_state(indoc! {"
23147        <span><span></span>ˇ</span>
23148    "});
23149    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23150    cx.assert_editor_state(indoc! {"
23151        <span><span></span>
23152        ˇ</span>
23153    "});
23154
23155    cx.set_state(indoc! {"
23156        <span>ˇ
23157        </span>
23158    "});
23159    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23160    cx.assert_editor_state(indoc! {"
23161        <span>
23162        ˇ
23163        </span>
23164    "});
23165}
23166
23167#[gpui::test(iterations = 10)]
23168async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23169    init_test(cx, |_| {});
23170
23171    let fs = FakeFs::new(cx.executor());
23172    fs.insert_tree(
23173        path!("/dir"),
23174        json!({
23175            "a.ts": "a",
23176        }),
23177    )
23178    .await;
23179
23180    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23181    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23182    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23183
23184    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23185    language_registry.add(Arc::new(Language::new(
23186        LanguageConfig {
23187            name: "TypeScript".into(),
23188            matcher: LanguageMatcher {
23189                path_suffixes: vec!["ts".to_string()],
23190                ..Default::default()
23191            },
23192            ..Default::default()
23193        },
23194        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
23195    )));
23196    let mut fake_language_servers = language_registry.register_fake_lsp(
23197        "TypeScript",
23198        FakeLspAdapter {
23199            capabilities: lsp::ServerCapabilities {
23200                code_lens_provider: Some(lsp::CodeLensOptions {
23201                    resolve_provider: Some(true),
23202                }),
23203                execute_command_provider: Some(lsp::ExecuteCommandOptions {
23204                    commands: vec!["_the/command".to_string()],
23205                    ..lsp::ExecuteCommandOptions::default()
23206                }),
23207                ..lsp::ServerCapabilities::default()
23208            },
23209            ..FakeLspAdapter::default()
23210        },
23211    );
23212
23213    let editor = workspace
23214        .update(cx, |workspace, window, cx| {
23215            workspace.open_abs_path(
23216                PathBuf::from(path!("/dir/a.ts")),
23217                OpenOptions::default(),
23218                window,
23219                cx,
23220            )
23221        })
23222        .unwrap()
23223        .await
23224        .unwrap()
23225        .downcast::<Editor>()
23226        .unwrap();
23227    cx.executor().run_until_parked();
23228
23229    let fake_server = fake_language_servers.next().await.unwrap();
23230
23231    let buffer = editor.update(cx, |editor, cx| {
23232        editor
23233            .buffer()
23234            .read(cx)
23235            .as_singleton()
23236            .expect("have opened a single file by path")
23237    });
23238
23239    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
23240    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
23241    drop(buffer_snapshot);
23242    let actions = cx
23243        .update_window(*workspace, |_, window, cx| {
23244            project.code_actions(&buffer, anchor..anchor, window, cx)
23245        })
23246        .unwrap();
23247
23248    fake_server
23249        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23250            Ok(Some(vec![
23251                lsp::CodeLens {
23252                    range: lsp::Range::default(),
23253                    command: Some(lsp::Command {
23254                        title: "Code lens command".to_owned(),
23255                        command: "_the/command".to_owned(),
23256                        arguments: None,
23257                    }),
23258                    data: None,
23259                },
23260                lsp::CodeLens {
23261                    range: lsp::Range::default(),
23262                    command: Some(lsp::Command {
23263                        title: "Command not in capabilities".to_owned(),
23264                        command: "not in capabilities".to_owned(),
23265                        arguments: None,
23266                    }),
23267                    data: None,
23268                },
23269                lsp::CodeLens {
23270                    range: lsp::Range {
23271                        start: lsp::Position {
23272                            line: 1,
23273                            character: 1,
23274                        },
23275                        end: lsp::Position {
23276                            line: 1,
23277                            character: 1,
23278                        },
23279                    },
23280                    command: Some(lsp::Command {
23281                        title: "Command not in range".to_owned(),
23282                        command: "_the/command".to_owned(),
23283                        arguments: None,
23284                    }),
23285                    data: None,
23286                },
23287            ]))
23288        })
23289        .next()
23290        .await;
23291
23292    let actions = actions.await.unwrap();
23293    assert_eq!(
23294        actions.len(),
23295        1,
23296        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
23297    );
23298    let action = actions[0].clone();
23299    let apply = project.update(cx, |project, cx| {
23300        project.apply_code_action(buffer.clone(), action, true, cx)
23301    });
23302
23303    // Resolving the code action does not populate its edits. In absence of
23304    // edits, we must execute the given command.
23305    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
23306        |mut lens, _| async move {
23307            let lens_command = lens.command.as_mut().expect("should have a command");
23308            assert_eq!(lens_command.title, "Code lens command");
23309            lens_command.arguments = Some(vec![json!("the-argument")]);
23310            Ok(lens)
23311        },
23312    );
23313
23314    // While executing the command, the language server sends the editor
23315    // a `workspaceEdit` request.
23316    fake_server
23317        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
23318            let fake = fake_server.clone();
23319            move |params, _| {
23320                assert_eq!(params.command, "_the/command");
23321                let fake = fake.clone();
23322                async move {
23323                    fake.server
23324                        .request::<lsp::request::ApplyWorkspaceEdit>(
23325                            lsp::ApplyWorkspaceEditParams {
23326                                label: None,
23327                                edit: lsp::WorkspaceEdit {
23328                                    changes: Some(
23329                                        [(
23330                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
23331                                            vec![lsp::TextEdit {
23332                                                range: lsp::Range::new(
23333                                                    lsp::Position::new(0, 0),
23334                                                    lsp::Position::new(0, 0),
23335                                                ),
23336                                                new_text: "X".into(),
23337                                            }],
23338                                        )]
23339                                        .into_iter()
23340                                        .collect(),
23341                                    ),
23342                                    ..lsp::WorkspaceEdit::default()
23343                                },
23344                            },
23345                        )
23346                        .await
23347                        .into_response()
23348                        .unwrap();
23349                    Ok(Some(json!(null)))
23350                }
23351            }
23352        })
23353        .next()
23354        .await;
23355
23356    // Applying the code lens command returns a project transaction containing the edits
23357    // sent by the language server in its `workspaceEdit` request.
23358    let transaction = apply.await.unwrap();
23359    assert!(transaction.0.contains_key(&buffer));
23360    buffer.update(cx, |buffer, cx| {
23361        assert_eq!(buffer.text(), "Xa");
23362        buffer.undo(cx);
23363        assert_eq!(buffer.text(), "a");
23364    });
23365
23366    let actions_after_edits = cx
23367        .update_window(*workspace, |_, window, cx| {
23368            project.code_actions(&buffer, anchor..anchor, window, cx)
23369        })
23370        .unwrap()
23371        .await
23372        .unwrap();
23373    assert_eq!(
23374        actions, actions_after_edits,
23375        "For the same selection, same code lens actions should be returned"
23376    );
23377
23378    let _responses =
23379        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23380            panic!("No more code lens requests are expected");
23381        });
23382    editor.update_in(cx, |editor, window, cx| {
23383        editor.select_all(&SelectAll, window, cx);
23384    });
23385    cx.executor().run_until_parked();
23386    let new_actions = cx
23387        .update_window(*workspace, |_, window, cx| {
23388            project.code_actions(&buffer, anchor..anchor, window, cx)
23389        })
23390        .unwrap()
23391        .await
23392        .unwrap();
23393    assert_eq!(
23394        actions, new_actions,
23395        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
23396    );
23397}
23398
23399#[gpui::test]
23400async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
23401    init_test(cx, |_| {});
23402
23403    let fs = FakeFs::new(cx.executor());
23404    let main_text = r#"fn main() {
23405println!("1");
23406println!("2");
23407println!("3");
23408println!("4");
23409println!("5");
23410}"#;
23411    let lib_text = "mod foo {}";
23412    fs.insert_tree(
23413        path!("/a"),
23414        json!({
23415            "lib.rs": lib_text,
23416            "main.rs": main_text,
23417        }),
23418    )
23419    .await;
23420
23421    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23422    let (workspace, cx) =
23423        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23424    let worktree_id = workspace.update(cx, |workspace, cx| {
23425        workspace.project().update(cx, |project, cx| {
23426            project.worktrees(cx).next().unwrap().read(cx).id()
23427        })
23428    });
23429
23430    let expected_ranges = vec![
23431        Point::new(0, 0)..Point::new(0, 0),
23432        Point::new(1, 0)..Point::new(1, 1),
23433        Point::new(2, 0)..Point::new(2, 2),
23434        Point::new(3, 0)..Point::new(3, 3),
23435    ];
23436
23437    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23438    let editor_1 = workspace
23439        .update_in(cx, |workspace, window, cx| {
23440            workspace.open_path(
23441                (worktree_id, rel_path("main.rs")),
23442                Some(pane_1.downgrade()),
23443                true,
23444                window,
23445                cx,
23446            )
23447        })
23448        .unwrap()
23449        .await
23450        .downcast::<Editor>()
23451        .unwrap();
23452    pane_1.update(cx, |pane, cx| {
23453        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23454        open_editor.update(cx, |editor, cx| {
23455            assert_eq!(
23456                editor.display_text(cx),
23457                main_text,
23458                "Original main.rs text on initial open",
23459            );
23460            assert_eq!(
23461                editor
23462                    .selections
23463                    .all::<Point>(cx)
23464                    .into_iter()
23465                    .map(|s| s.range())
23466                    .collect::<Vec<_>>(),
23467                vec![Point::zero()..Point::zero()],
23468                "Default selections on initial open",
23469            );
23470        })
23471    });
23472    editor_1.update_in(cx, |editor, window, cx| {
23473        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23474            s.select_ranges(expected_ranges.clone());
23475        });
23476    });
23477
23478    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
23479        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
23480    });
23481    let editor_2 = workspace
23482        .update_in(cx, |workspace, window, cx| {
23483            workspace.open_path(
23484                (worktree_id, rel_path("main.rs")),
23485                Some(pane_2.downgrade()),
23486                true,
23487                window,
23488                cx,
23489            )
23490        })
23491        .unwrap()
23492        .await
23493        .downcast::<Editor>()
23494        .unwrap();
23495    pane_2.update(cx, |pane, cx| {
23496        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23497        open_editor.update(cx, |editor, cx| {
23498            assert_eq!(
23499                editor.display_text(cx),
23500                main_text,
23501                "Original main.rs text on initial open in another panel",
23502            );
23503            assert_eq!(
23504                editor
23505                    .selections
23506                    .all::<Point>(cx)
23507                    .into_iter()
23508                    .map(|s| s.range())
23509                    .collect::<Vec<_>>(),
23510                vec![Point::zero()..Point::zero()],
23511                "Default selections on initial open in another panel",
23512            );
23513        })
23514    });
23515
23516    editor_2.update_in(cx, |editor, window, cx| {
23517        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23518    });
23519
23520    let _other_editor_1 = workspace
23521        .update_in(cx, |workspace, window, cx| {
23522            workspace.open_path(
23523                (worktree_id, rel_path("lib.rs")),
23524                Some(pane_1.downgrade()),
23525                true,
23526                window,
23527                cx,
23528            )
23529        })
23530        .unwrap()
23531        .await
23532        .downcast::<Editor>()
23533        .unwrap();
23534    pane_1
23535        .update_in(cx, |pane, window, cx| {
23536            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23537        })
23538        .await
23539        .unwrap();
23540    drop(editor_1);
23541    pane_1.update(cx, |pane, cx| {
23542        pane.active_item()
23543            .unwrap()
23544            .downcast::<Editor>()
23545            .unwrap()
23546            .update(cx, |editor, cx| {
23547                assert_eq!(
23548                    editor.display_text(cx),
23549                    lib_text,
23550                    "Other file should be open and active",
23551                );
23552            });
23553        assert_eq!(pane.items().count(), 1, "No other editors should be open");
23554    });
23555
23556    let _other_editor_2 = workspace
23557        .update_in(cx, |workspace, window, cx| {
23558            workspace.open_path(
23559                (worktree_id, rel_path("lib.rs")),
23560                Some(pane_2.downgrade()),
23561                true,
23562                window,
23563                cx,
23564            )
23565        })
23566        .unwrap()
23567        .await
23568        .downcast::<Editor>()
23569        .unwrap();
23570    pane_2
23571        .update_in(cx, |pane, window, cx| {
23572            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23573        })
23574        .await
23575        .unwrap();
23576    drop(editor_2);
23577    pane_2.update(cx, |pane, cx| {
23578        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23579        open_editor.update(cx, |editor, cx| {
23580            assert_eq!(
23581                editor.display_text(cx),
23582                lib_text,
23583                "Other file should be open and active in another panel too",
23584            );
23585        });
23586        assert_eq!(
23587            pane.items().count(),
23588            1,
23589            "No other editors should be open in another pane",
23590        );
23591    });
23592
23593    let _editor_1_reopened = workspace
23594        .update_in(cx, |workspace, window, cx| {
23595            workspace.open_path(
23596                (worktree_id, rel_path("main.rs")),
23597                Some(pane_1.downgrade()),
23598                true,
23599                window,
23600                cx,
23601            )
23602        })
23603        .unwrap()
23604        .await
23605        .downcast::<Editor>()
23606        .unwrap();
23607    let _editor_2_reopened = workspace
23608        .update_in(cx, |workspace, window, cx| {
23609            workspace.open_path(
23610                (worktree_id, rel_path("main.rs")),
23611                Some(pane_2.downgrade()),
23612                true,
23613                window,
23614                cx,
23615            )
23616        })
23617        .unwrap()
23618        .await
23619        .downcast::<Editor>()
23620        .unwrap();
23621    pane_1.update(cx, |pane, cx| {
23622        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23623        open_editor.update(cx, |editor, cx| {
23624            assert_eq!(
23625                editor.display_text(cx),
23626                main_text,
23627                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
23628            );
23629            assert_eq!(
23630                editor
23631                    .selections
23632                    .all::<Point>(cx)
23633                    .into_iter()
23634                    .map(|s| s.range())
23635                    .collect::<Vec<_>>(),
23636                expected_ranges,
23637                "Previous editor in the 1st panel had selections and should get them restored on reopen",
23638            );
23639        })
23640    });
23641    pane_2.update(cx, |pane, cx| {
23642        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23643        open_editor.update(cx, |editor, cx| {
23644            assert_eq!(
23645                editor.display_text(cx),
23646                r#"fn main() {
23647⋯rintln!("1");
23648⋯intln!("2");
23649⋯ntln!("3");
23650println!("4");
23651println!("5");
23652}"#,
23653                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
23654            );
23655            assert_eq!(
23656                editor
23657                    .selections
23658                    .all::<Point>(cx)
23659                    .into_iter()
23660                    .map(|s| s.range())
23661                    .collect::<Vec<_>>(),
23662                vec![Point::zero()..Point::zero()],
23663                "Previous editor in the 2nd pane had no selections changed hence should restore none",
23664            );
23665        })
23666    });
23667}
23668
23669#[gpui::test]
23670async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
23671    init_test(cx, |_| {});
23672
23673    let fs = FakeFs::new(cx.executor());
23674    let main_text = r#"fn main() {
23675println!("1");
23676println!("2");
23677println!("3");
23678println!("4");
23679println!("5");
23680}"#;
23681    let lib_text = "mod foo {}";
23682    fs.insert_tree(
23683        path!("/a"),
23684        json!({
23685            "lib.rs": lib_text,
23686            "main.rs": main_text,
23687        }),
23688    )
23689    .await;
23690
23691    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23692    let (workspace, cx) =
23693        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23694    let worktree_id = workspace.update(cx, |workspace, cx| {
23695        workspace.project().update(cx, |project, cx| {
23696            project.worktrees(cx).next().unwrap().read(cx).id()
23697        })
23698    });
23699
23700    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23701    let editor = workspace
23702        .update_in(cx, |workspace, window, cx| {
23703            workspace.open_path(
23704                (worktree_id, rel_path("main.rs")),
23705                Some(pane.downgrade()),
23706                true,
23707                window,
23708                cx,
23709            )
23710        })
23711        .unwrap()
23712        .await
23713        .downcast::<Editor>()
23714        .unwrap();
23715    pane.update(cx, |pane, cx| {
23716        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23717        open_editor.update(cx, |editor, cx| {
23718            assert_eq!(
23719                editor.display_text(cx),
23720                main_text,
23721                "Original main.rs text on initial open",
23722            );
23723        })
23724    });
23725    editor.update_in(cx, |editor, window, cx| {
23726        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
23727    });
23728
23729    cx.update_global(|store: &mut SettingsStore, cx| {
23730        store.update_user_settings(cx, |s| {
23731            s.workspace.restore_on_file_reopen = Some(false);
23732        });
23733    });
23734    editor.update_in(cx, |editor, window, cx| {
23735        editor.fold_ranges(
23736            vec![
23737                Point::new(1, 0)..Point::new(1, 1),
23738                Point::new(2, 0)..Point::new(2, 2),
23739                Point::new(3, 0)..Point::new(3, 3),
23740            ],
23741            false,
23742            window,
23743            cx,
23744        );
23745    });
23746    pane.update_in(cx, |pane, window, cx| {
23747        pane.close_all_items(&CloseAllItems::default(), window, cx)
23748    })
23749    .await
23750    .unwrap();
23751    pane.update(cx, |pane, _| {
23752        assert!(pane.active_item().is_none());
23753    });
23754    cx.update_global(|store: &mut SettingsStore, cx| {
23755        store.update_user_settings(cx, |s| {
23756            s.workspace.restore_on_file_reopen = Some(true);
23757        });
23758    });
23759
23760    let _editor_reopened = workspace
23761        .update_in(cx, |workspace, window, cx| {
23762            workspace.open_path(
23763                (worktree_id, rel_path("main.rs")),
23764                Some(pane.downgrade()),
23765                true,
23766                window,
23767                cx,
23768            )
23769        })
23770        .unwrap()
23771        .await
23772        .downcast::<Editor>()
23773        .unwrap();
23774    pane.update(cx, |pane, cx| {
23775        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23776        open_editor.update(cx, |editor, cx| {
23777            assert_eq!(
23778                editor.display_text(cx),
23779                main_text,
23780                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
23781            );
23782        })
23783    });
23784}
23785
23786#[gpui::test]
23787async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
23788    struct EmptyModalView {
23789        focus_handle: gpui::FocusHandle,
23790    }
23791    impl EventEmitter<DismissEvent> for EmptyModalView {}
23792    impl Render for EmptyModalView {
23793        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
23794            div()
23795        }
23796    }
23797    impl Focusable for EmptyModalView {
23798        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
23799            self.focus_handle.clone()
23800        }
23801    }
23802    impl workspace::ModalView for EmptyModalView {}
23803    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
23804        EmptyModalView {
23805            focus_handle: cx.focus_handle(),
23806        }
23807    }
23808
23809    init_test(cx, |_| {});
23810
23811    let fs = FakeFs::new(cx.executor());
23812    let project = Project::test(fs, [], cx).await;
23813    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23814    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
23815    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23816    let editor = cx.new_window_entity(|window, cx| {
23817        Editor::new(
23818            EditorMode::full(),
23819            buffer,
23820            Some(project.clone()),
23821            window,
23822            cx,
23823        )
23824    });
23825    workspace
23826        .update(cx, |workspace, window, cx| {
23827            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
23828        })
23829        .unwrap();
23830    editor.update_in(cx, |editor, window, cx| {
23831        editor.open_context_menu(&OpenContextMenu, window, cx);
23832        assert!(editor.mouse_context_menu.is_some());
23833    });
23834    workspace
23835        .update(cx, |workspace, window, cx| {
23836            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
23837        })
23838        .unwrap();
23839    cx.read(|cx| {
23840        assert!(editor.read(cx).mouse_context_menu.is_none());
23841    });
23842}
23843
23844fn set_linked_edit_ranges(
23845    opening: (Point, Point),
23846    closing: (Point, Point),
23847    editor: &mut Editor,
23848    cx: &mut Context<Editor>,
23849) {
23850    let Some((buffer, _)) = editor
23851        .buffer
23852        .read(cx)
23853        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
23854    else {
23855        panic!("Failed to get buffer for selection position");
23856    };
23857    let buffer = buffer.read(cx);
23858    let buffer_id = buffer.remote_id();
23859    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
23860    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
23861    let mut linked_ranges = HashMap::default();
23862    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
23863    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
23864}
23865
23866#[gpui::test]
23867async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
23868    init_test(cx, |_| {});
23869
23870    let fs = FakeFs::new(cx.executor());
23871    fs.insert_file(path!("/file.html"), Default::default())
23872        .await;
23873
23874    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
23875
23876    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23877    let html_language = Arc::new(Language::new(
23878        LanguageConfig {
23879            name: "HTML".into(),
23880            matcher: LanguageMatcher {
23881                path_suffixes: vec!["html".to_string()],
23882                ..LanguageMatcher::default()
23883            },
23884            brackets: BracketPairConfig {
23885                pairs: vec![BracketPair {
23886                    start: "<".into(),
23887                    end: ">".into(),
23888                    close: true,
23889                    ..Default::default()
23890                }],
23891                ..Default::default()
23892            },
23893            ..Default::default()
23894        },
23895        Some(tree_sitter_html::LANGUAGE.into()),
23896    ));
23897    language_registry.add(html_language);
23898    let mut fake_servers = language_registry.register_fake_lsp(
23899        "HTML",
23900        FakeLspAdapter {
23901            capabilities: lsp::ServerCapabilities {
23902                completion_provider: Some(lsp::CompletionOptions {
23903                    resolve_provider: Some(true),
23904                    ..Default::default()
23905                }),
23906                ..Default::default()
23907            },
23908            ..Default::default()
23909        },
23910    );
23911
23912    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23913    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23914
23915    let worktree_id = workspace
23916        .update(cx, |workspace, _window, cx| {
23917            workspace.project().update(cx, |project, cx| {
23918                project.worktrees(cx).next().unwrap().read(cx).id()
23919            })
23920        })
23921        .unwrap();
23922    project
23923        .update(cx, |project, cx| {
23924            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
23925        })
23926        .await
23927        .unwrap();
23928    let editor = workspace
23929        .update(cx, |workspace, window, cx| {
23930            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
23931        })
23932        .unwrap()
23933        .await
23934        .unwrap()
23935        .downcast::<Editor>()
23936        .unwrap();
23937
23938    let fake_server = fake_servers.next().await.unwrap();
23939    editor.update_in(cx, |editor, window, cx| {
23940        editor.set_text("<ad></ad>", window, cx);
23941        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23942            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
23943        });
23944        set_linked_edit_ranges(
23945            (Point::new(0, 1), Point::new(0, 3)),
23946            (Point::new(0, 6), Point::new(0, 8)),
23947            editor,
23948            cx,
23949        );
23950    });
23951    let mut completion_handle =
23952        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
23953            Ok(Some(lsp::CompletionResponse::Array(vec![
23954                lsp::CompletionItem {
23955                    label: "head".to_string(),
23956                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23957                        lsp::InsertReplaceEdit {
23958                            new_text: "head".to_string(),
23959                            insert: lsp::Range::new(
23960                                lsp::Position::new(0, 1),
23961                                lsp::Position::new(0, 3),
23962                            ),
23963                            replace: lsp::Range::new(
23964                                lsp::Position::new(0, 1),
23965                                lsp::Position::new(0, 3),
23966                            ),
23967                        },
23968                    )),
23969                    ..Default::default()
23970                },
23971            ])))
23972        });
23973    editor.update_in(cx, |editor, window, cx| {
23974        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
23975    });
23976    cx.run_until_parked();
23977    completion_handle.next().await.unwrap();
23978    editor.update(cx, |editor, _| {
23979        assert!(
23980            editor.context_menu_visible(),
23981            "Completion menu should be visible"
23982        );
23983    });
23984    editor.update_in(cx, |editor, window, cx| {
23985        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
23986    });
23987    cx.executor().run_until_parked();
23988    editor.update(cx, |editor, cx| {
23989        assert_eq!(editor.text(cx), "<head></head>");
23990    });
23991}
23992
23993#[gpui::test]
23994async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
23995    init_test(cx, |_| {});
23996
23997    let mut cx = EditorTestContext::new(cx).await;
23998    let language = Arc::new(Language::new(
23999        LanguageConfig {
24000            name: "TSX".into(),
24001            matcher: LanguageMatcher {
24002                path_suffixes: vec!["tsx".to_string()],
24003                ..LanguageMatcher::default()
24004            },
24005            brackets: BracketPairConfig {
24006                pairs: vec![BracketPair {
24007                    start: "<".into(),
24008                    end: ">".into(),
24009                    close: true,
24010                    ..Default::default()
24011                }],
24012                ..Default::default()
24013            },
24014            linked_edit_characters: HashSet::from_iter(['.']),
24015            ..Default::default()
24016        },
24017        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
24018    ));
24019    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24020
24021    // Test typing > does not extend linked pair
24022    cx.set_state("<divˇ<div></div>");
24023    cx.update_editor(|editor, _, cx| {
24024        set_linked_edit_ranges(
24025            (Point::new(0, 1), Point::new(0, 4)),
24026            (Point::new(0, 11), Point::new(0, 14)),
24027            editor,
24028            cx,
24029        );
24030    });
24031    cx.update_editor(|editor, window, cx| {
24032        editor.handle_input(">", window, cx);
24033    });
24034    cx.assert_editor_state("<div>ˇ<div></div>");
24035
24036    // Test typing . do extend linked pair
24037    cx.set_state("<Animatedˇ></Animated>");
24038    cx.update_editor(|editor, _, cx| {
24039        set_linked_edit_ranges(
24040            (Point::new(0, 1), Point::new(0, 9)),
24041            (Point::new(0, 12), Point::new(0, 20)),
24042            editor,
24043            cx,
24044        );
24045    });
24046    cx.update_editor(|editor, window, cx| {
24047        editor.handle_input(".", window, cx);
24048    });
24049    cx.assert_editor_state("<Animated.ˇ></Animated.>");
24050    cx.update_editor(|editor, _, cx| {
24051        set_linked_edit_ranges(
24052            (Point::new(0, 1), Point::new(0, 10)),
24053            (Point::new(0, 13), Point::new(0, 21)),
24054            editor,
24055            cx,
24056        );
24057    });
24058    cx.update_editor(|editor, window, cx| {
24059        editor.handle_input("V", window, cx);
24060    });
24061    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
24062}
24063
24064#[gpui::test]
24065async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
24066    init_test(cx, |_| {});
24067
24068    let fs = FakeFs::new(cx.executor());
24069    fs.insert_tree(
24070        path!("/root"),
24071        json!({
24072            "a": {
24073                "main.rs": "fn main() {}",
24074            },
24075            "foo": {
24076                "bar": {
24077                    "external_file.rs": "pub mod external {}",
24078                }
24079            }
24080        }),
24081    )
24082    .await;
24083
24084    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
24085    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24086    language_registry.add(rust_lang());
24087    let _fake_servers = language_registry.register_fake_lsp(
24088        "Rust",
24089        FakeLspAdapter {
24090            ..FakeLspAdapter::default()
24091        },
24092    );
24093    let (workspace, cx) =
24094        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24095    let worktree_id = workspace.update(cx, |workspace, cx| {
24096        workspace.project().update(cx, |project, cx| {
24097            project.worktrees(cx).next().unwrap().read(cx).id()
24098        })
24099    });
24100
24101    let assert_language_servers_count =
24102        |expected: usize, context: &str, cx: &mut VisualTestContext| {
24103            project.update(cx, |project, cx| {
24104                let current = project
24105                    .lsp_store()
24106                    .read(cx)
24107                    .as_local()
24108                    .unwrap()
24109                    .language_servers
24110                    .len();
24111                assert_eq!(expected, current, "{context}");
24112            });
24113        };
24114
24115    assert_language_servers_count(
24116        0,
24117        "No servers should be running before any file is open",
24118        cx,
24119    );
24120    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24121    let main_editor = workspace
24122        .update_in(cx, |workspace, window, cx| {
24123            workspace.open_path(
24124                (worktree_id, rel_path("main.rs")),
24125                Some(pane.downgrade()),
24126                true,
24127                window,
24128                cx,
24129            )
24130        })
24131        .unwrap()
24132        .await
24133        .downcast::<Editor>()
24134        .unwrap();
24135    pane.update(cx, |pane, cx| {
24136        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24137        open_editor.update(cx, |editor, cx| {
24138            assert_eq!(
24139                editor.display_text(cx),
24140                "fn main() {}",
24141                "Original main.rs text on initial open",
24142            );
24143        });
24144        assert_eq!(open_editor, main_editor);
24145    });
24146    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24147
24148    let external_editor = workspace
24149        .update_in(cx, |workspace, window, cx| {
24150            workspace.open_abs_path(
24151                PathBuf::from("/root/foo/bar/external_file.rs"),
24152                OpenOptions::default(),
24153                window,
24154                cx,
24155            )
24156        })
24157        .await
24158        .expect("opening external file")
24159        .downcast::<Editor>()
24160        .expect("downcasted external file's open element to editor");
24161    pane.update(cx, |pane, cx| {
24162        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24163        open_editor.update(cx, |editor, cx| {
24164            assert_eq!(
24165                editor.display_text(cx),
24166                "pub mod external {}",
24167                "External file is open now",
24168            );
24169        });
24170        assert_eq!(open_editor, external_editor);
24171    });
24172    assert_language_servers_count(
24173        1,
24174        "Second, external, *.rs file should join the existing server",
24175        cx,
24176    );
24177
24178    pane.update_in(cx, |pane, window, cx| {
24179        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24180    })
24181    .await
24182    .unwrap();
24183    pane.update_in(cx, |pane, window, cx| {
24184        pane.navigate_backward(&Default::default(), window, cx);
24185    });
24186    cx.run_until_parked();
24187    pane.update(cx, |pane, cx| {
24188        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24189        open_editor.update(cx, |editor, cx| {
24190            assert_eq!(
24191                editor.display_text(cx),
24192                "pub mod external {}",
24193                "External file is open now",
24194            );
24195        });
24196    });
24197    assert_language_servers_count(
24198        1,
24199        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
24200        cx,
24201    );
24202
24203    cx.update(|_, cx| {
24204        workspace::reload(cx);
24205    });
24206    assert_language_servers_count(
24207        1,
24208        "After reloading the worktree with local and external files opened, only one project should be started",
24209        cx,
24210    );
24211}
24212
24213#[gpui::test]
24214async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
24215    init_test(cx, |_| {});
24216
24217    let mut cx = EditorTestContext::new(cx).await;
24218    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24219    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24220
24221    // test cursor move to start of each line on tab
24222    // for `if`, `elif`, `else`, `while`, `with` and `for`
24223    cx.set_state(indoc! {"
24224        def main():
24225        ˇ    for item in items:
24226        ˇ        while item.active:
24227        ˇ            if item.value > 10:
24228        ˇ                continue
24229        ˇ            elif item.value < 0:
24230        ˇ                break
24231        ˇ            else:
24232        ˇ                with item.context() as ctx:
24233        ˇ                    yield count
24234        ˇ        else:
24235        ˇ            log('while else')
24236        ˇ    else:
24237        ˇ        log('for else')
24238    "});
24239    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24240    cx.assert_editor_state(indoc! {"
24241        def main():
24242            ˇfor item in items:
24243                ˇwhile item.active:
24244                    ˇif item.value > 10:
24245                        ˇcontinue
24246                    ˇelif item.value < 0:
24247                        ˇbreak
24248                    ˇelse:
24249                        ˇwith item.context() as ctx:
24250                            ˇyield count
24251                ˇelse:
24252                    ˇlog('while else')
24253            ˇelse:
24254                ˇlog('for else')
24255    "});
24256    // test relative indent is preserved when tab
24257    // for `if`, `elif`, `else`, `while`, `with` and `for`
24258    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24259    cx.assert_editor_state(indoc! {"
24260        def main():
24261                ˇfor item in items:
24262                    ˇwhile item.active:
24263                        ˇif item.value > 10:
24264                            ˇcontinue
24265                        ˇelif item.value < 0:
24266                            ˇbreak
24267                        ˇelse:
24268                            ˇwith item.context() as ctx:
24269                                ˇyield count
24270                    ˇelse:
24271                        ˇlog('while else')
24272                ˇelse:
24273                    ˇlog('for else')
24274    "});
24275
24276    // test cursor move to start of each line on tab
24277    // for `try`, `except`, `else`, `finally`, `match` and `def`
24278    cx.set_state(indoc! {"
24279        def main():
24280        ˇ    try:
24281        ˇ        fetch()
24282        ˇ    except ValueError:
24283        ˇ        handle_error()
24284        ˇ    else:
24285        ˇ        match value:
24286        ˇ            case _:
24287        ˇ    finally:
24288        ˇ        def status():
24289        ˇ            return 0
24290    "});
24291    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24292    cx.assert_editor_state(indoc! {"
24293        def main():
24294            ˇtry:
24295                ˇfetch()
24296            ˇexcept ValueError:
24297                ˇhandle_error()
24298            ˇelse:
24299                ˇmatch value:
24300                    ˇcase _:
24301            ˇfinally:
24302                ˇdef status():
24303                    ˇreturn 0
24304    "});
24305    // test relative indent is preserved when tab
24306    // for `try`, `except`, `else`, `finally`, `match` and `def`
24307    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24308    cx.assert_editor_state(indoc! {"
24309        def main():
24310                ˇtry:
24311                    ˇfetch()
24312                ˇexcept ValueError:
24313                    ˇhandle_error()
24314                ˇelse:
24315                    ˇmatch value:
24316                        ˇcase _:
24317                ˇfinally:
24318                    ˇdef status():
24319                        ˇreturn 0
24320    "});
24321}
24322
24323#[gpui::test]
24324async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
24325    init_test(cx, |_| {});
24326
24327    let mut cx = EditorTestContext::new(cx).await;
24328    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24329    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24330
24331    // test `else` auto outdents when typed inside `if` block
24332    cx.set_state(indoc! {"
24333        def main():
24334            if i == 2:
24335                return
24336                ˇ
24337    "});
24338    cx.update_editor(|editor, window, cx| {
24339        editor.handle_input("else:", window, cx);
24340    });
24341    cx.assert_editor_state(indoc! {"
24342        def main():
24343            if i == 2:
24344                return
24345            else:ˇ
24346    "});
24347
24348    // test `except` auto outdents when typed inside `try` block
24349    cx.set_state(indoc! {"
24350        def main():
24351            try:
24352                i = 2
24353                ˇ
24354    "});
24355    cx.update_editor(|editor, window, cx| {
24356        editor.handle_input("except:", window, cx);
24357    });
24358    cx.assert_editor_state(indoc! {"
24359        def main():
24360            try:
24361                i = 2
24362            except:ˇ
24363    "});
24364
24365    // test `else` auto outdents when typed inside `except` block
24366    cx.set_state(indoc! {"
24367        def main():
24368            try:
24369                i = 2
24370            except:
24371                j = 2
24372                ˇ
24373    "});
24374    cx.update_editor(|editor, window, cx| {
24375        editor.handle_input("else:", window, cx);
24376    });
24377    cx.assert_editor_state(indoc! {"
24378        def main():
24379            try:
24380                i = 2
24381            except:
24382                j = 2
24383            else:ˇ
24384    "});
24385
24386    // test `finally` auto outdents when typed inside `else` block
24387    cx.set_state(indoc! {"
24388        def main():
24389            try:
24390                i = 2
24391            except:
24392                j = 2
24393            else:
24394                k = 2
24395                ˇ
24396    "});
24397    cx.update_editor(|editor, window, cx| {
24398        editor.handle_input("finally:", window, cx);
24399    });
24400    cx.assert_editor_state(indoc! {"
24401        def main():
24402            try:
24403                i = 2
24404            except:
24405                j = 2
24406            else:
24407                k = 2
24408            finally:ˇ
24409    "});
24410
24411    // test `else` does not outdents when typed inside `except` block right after for block
24412    cx.set_state(indoc! {"
24413        def main():
24414            try:
24415                i = 2
24416            except:
24417                for i in range(n):
24418                    pass
24419                ˇ
24420    "});
24421    cx.update_editor(|editor, window, cx| {
24422        editor.handle_input("else:", window, cx);
24423    });
24424    cx.assert_editor_state(indoc! {"
24425        def main():
24426            try:
24427                i = 2
24428            except:
24429                for i in range(n):
24430                    pass
24431                else:ˇ
24432    "});
24433
24434    // test `finally` auto outdents when typed inside `else` block right after for block
24435    cx.set_state(indoc! {"
24436        def main():
24437            try:
24438                i = 2
24439            except:
24440                j = 2
24441            else:
24442                for i in range(n):
24443                    pass
24444                ˇ
24445    "});
24446    cx.update_editor(|editor, window, cx| {
24447        editor.handle_input("finally:", window, cx);
24448    });
24449    cx.assert_editor_state(indoc! {"
24450        def main():
24451            try:
24452                i = 2
24453            except:
24454                j = 2
24455            else:
24456                for i in range(n):
24457                    pass
24458            finally:ˇ
24459    "});
24460
24461    // test `except` outdents to inner "try" block
24462    cx.set_state(indoc! {"
24463        def main():
24464            try:
24465                i = 2
24466                if i == 2:
24467                    try:
24468                        i = 3
24469                        ˇ
24470    "});
24471    cx.update_editor(|editor, window, cx| {
24472        editor.handle_input("except:", window, cx);
24473    });
24474    cx.assert_editor_state(indoc! {"
24475        def main():
24476            try:
24477                i = 2
24478                if i == 2:
24479                    try:
24480                        i = 3
24481                    except:ˇ
24482    "});
24483
24484    // test `except` outdents to outer "try" block
24485    cx.set_state(indoc! {"
24486        def main():
24487            try:
24488                i = 2
24489                if i == 2:
24490                    try:
24491                        i = 3
24492                ˇ
24493    "});
24494    cx.update_editor(|editor, window, cx| {
24495        editor.handle_input("except:", window, cx);
24496    });
24497    cx.assert_editor_state(indoc! {"
24498        def main():
24499            try:
24500                i = 2
24501                if i == 2:
24502                    try:
24503                        i = 3
24504            except:ˇ
24505    "});
24506
24507    // test `else` stays at correct indent when typed after `for` block
24508    cx.set_state(indoc! {"
24509        def main():
24510            for i in range(10):
24511                if i == 3:
24512                    break
24513            ˇ
24514    "});
24515    cx.update_editor(|editor, window, cx| {
24516        editor.handle_input("else:", window, cx);
24517    });
24518    cx.assert_editor_state(indoc! {"
24519        def main():
24520            for i in range(10):
24521                if i == 3:
24522                    break
24523            else:ˇ
24524    "});
24525
24526    // test does not outdent on typing after line with square brackets
24527    cx.set_state(indoc! {"
24528        def f() -> list[str]:
24529            ˇ
24530    "});
24531    cx.update_editor(|editor, window, cx| {
24532        editor.handle_input("a", window, cx);
24533    });
24534    cx.assert_editor_state(indoc! {"
24535        def f() -> list[str]:
2453624537    "});
24538
24539    // test does not outdent on typing : after case keyword
24540    cx.set_state(indoc! {"
24541        match 1:
24542            caseˇ
24543    "});
24544    cx.update_editor(|editor, window, cx| {
24545        editor.handle_input(":", window, cx);
24546    });
24547    cx.assert_editor_state(indoc! {"
24548        match 1:
24549            case:ˇ
24550    "});
24551}
24552
24553#[gpui::test]
24554async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
24555    init_test(cx, |_| {});
24556    update_test_language_settings(cx, |settings| {
24557        settings.defaults.extend_comment_on_newline = Some(false);
24558    });
24559    let mut cx = EditorTestContext::new(cx).await;
24560    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24561    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24562
24563    // test correct indent after newline on comment
24564    cx.set_state(indoc! {"
24565        # COMMENT:ˇ
24566    "});
24567    cx.update_editor(|editor, window, cx| {
24568        editor.newline(&Newline, window, cx);
24569    });
24570    cx.assert_editor_state(indoc! {"
24571        # COMMENT:
24572        ˇ
24573    "});
24574
24575    // test correct indent after newline in brackets
24576    cx.set_state(indoc! {"
24577        {ˇ}
24578    "});
24579    cx.update_editor(|editor, window, cx| {
24580        editor.newline(&Newline, window, cx);
24581    });
24582    cx.run_until_parked();
24583    cx.assert_editor_state(indoc! {"
24584        {
24585            ˇ
24586        }
24587    "});
24588
24589    cx.set_state(indoc! {"
24590        (ˇ)
24591    "});
24592    cx.update_editor(|editor, window, cx| {
24593        editor.newline(&Newline, window, cx);
24594    });
24595    cx.run_until_parked();
24596    cx.assert_editor_state(indoc! {"
24597        (
24598            ˇ
24599        )
24600    "});
24601
24602    // do not indent after empty lists or dictionaries
24603    cx.set_state(indoc! {"
24604        a = []ˇ
24605    "});
24606    cx.update_editor(|editor, window, cx| {
24607        editor.newline(&Newline, window, cx);
24608    });
24609    cx.run_until_parked();
24610    cx.assert_editor_state(indoc! {"
24611        a = []
24612        ˇ
24613    "});
24614}
24615
24616#[gpui::test]
24617async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
24618    init_test(cx, |_| {});
24619
24620    let mut cx = EditorTestContext::new(cx).await;
24621    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24622    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24623
24624    // test cursor move to start of each line on tab
24625    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
24626    cx.set_state(indoc! {"
24627        function main() {
24628        ˇ    for item in $items; do
24629        ˇ        while [ -n \"$item\" ]; do
24630        ˇ            if [ \"$value\" -gt 10 ]; then
24631        ˇ                continue
24632        ˇ            elif [ \"$value\" -lt 0 ]; then
24633        ˇ                break
24634        ˇ            else
24635        ˇ                echo \"$item\"
24636        ˇ            fi
24637        ˇ        done
24638        ˇ    done
24639        ˇ}
24640    "});
24641    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24642    cx.assert_editor_state(indoc! {"
24643        function main() {
24644            ˇfor item in $items; do
24645                ˇwhile [ -n \"$item\" ]; do
24646                    ˇif [ \"$value\" -gt 10 ]; then
24647                        ˇcontinue
24648                    ˇelif [ \"$value\" -lt 0 ]; then
24649                        ˇbreak
24650                    ˇelse
24651                        ˇecho \"$item\"
24652                    ˇfi
24653                ˇdone
24654            ˇdone
24655        ˇ}
24656    "});
24657    // test relative indent is preserved when tab
24658    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24659    cx.assert_editor_state(indoc! {"
24660        function main() {
24661                ˇfor item in $items; do
24662                    ˇwhile [ -n \"$item\" ]; do
24663                        ˇif [ \"$value\" -gt 10 ]; then
24664                            ˇcontinue
24665                        ˇelif [ \"$value\" -lt 0 ]; then
24666                            ˇbreak
24667                        ˇelse
24668                            ˇecho \"$item\"
24669                        ˇfi
24670                    ˇdone
24671                ˇdone
24672            ˇ}
24673    "});
24674
24675    // test cursor move to start of each line on tab
24676    // for `case` statement with patterns
24677    cx.set_state(indoc! {"
24678        function handle() {
24679        ˇ    case \"$1\" in
24680        ˇ        start)
24681        ˇ            echo \"a\"
24682        ˇ            ;;
24683        ˇ        stop)
24684        ˇ            echo \"b\"
24685        ˇ            ;;
24686        ˇ        *)
24687        ˇ            echo \"c\"
24688        ˇ            ;;
24689        ˇ    esac
24690        ˇ}
24691    "});
24692    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24693    cx.assert_editor_state(indoc! {"
24694        function handle() {
24695            ˇcase \"$1\" in
24696                ˇstart)
24697                    ˇecho \"a\"
24698                    ˇ;;
24699                ˇstop)
24700                    ˇecho \"b\"
24701                    ˇ;;
24702                ˇ*)
24703                    ˇecho \"c\"
24704                    ˇ;;
24705            ˇesac
24706        ˇ}
24707    "});
24708}
24709
24710#[gpui::test]
24711async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
24712    init_test(cx, |_| {});
24713
24714    let mut cx = EditorTestContext::new(cx).await;
24715    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24716    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24717
24718    // test indents on comment insert
24719    cx.set_state(indoc! {"
24720        function main() {
24721        ˇ    for item in $items; do
24722        ˇ        while [ -n \"$item\" ]; do
24723        ˇ            if [ \"$value\" -gt 10 ]; then
24724        ˇ                continue
24725        ˇ            elif [ \"$value\" -lt 0 ]; then
24726        ˇ                break
24727        ˇ            else
24728        ˇ                echo \"$item\"
24729        ˇ            fi
24730        ˇ        done
24731        ˇ    done
24732        ˇ}
24733    "});
24734    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
24735    cx.assert_editor_state(indoc! {"
24736        function main() {
24737        #ˇ    for item in $items; do
24738        #ˇ        while [ -n \"$item\" ]; do
24739        #ˇ            if [ \"$value\" -gt 10 ]; then
24740        #ˇ                continue
24741        #ˇ            elif [ \"$value\" -lt 0 ]; then
24742        #ˇ                break
24743        #ˇ            else
24744        #ˇ                echo \"$item\"
24745        #ˇ            fi
24746        #ˇ        done
24747        #ˇ    done
24748        #ˇ}
24749    "});
24750}
24751
24752#[gpui::test]
24753async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
24754    init_test(cx, |_| {});
24755
24756    let mut cx = EditorTestContext::new(cx).await;
24757    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24758    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24759
24760    // test `else` auto outdents when typed inside `if` block
24761    cx.set_state(indoc! {"
24762        if [ \"$1\" = \"test\" ]; then
24763            echo \"foo bar\"
24764            ˇ
24765    "});
24766    cx.update_editor(|editor, window, cx| {
24767        editor.handle_input("else", window, cx);
24768    });
24769    cx.assert_editor_state(indoc! {"
24770        if [ \"$1\" = \"test\" ]; then
24771            echo \"foo bar\"
24772        elseˇ
24773    "});
24774
24775    // test `elif` auto outdents when typed inside `if` block
24776    cx.set_state(indoc! {"
24777        if [ \"$1\" = \"test\" ]; then
24778            echo \"foo bar\"
24779            ˇ
24780    "});
24781    cx.update_editor(|editor, window, cx| {
24782        editor.handle_input("elif", window, cx);
24783    });
24784    cx.assert_editor_state(indoc! {"
24785        if [ \"$1\" = \"test\" ]; then
24786            echo \"foo bar\"
24787        elifˇ
24788    "});
24789
24790    // test `fi` auto outdents when typed inside `else` block
24791    cx.set_state(indoc! {"
24792        if [ \"$1\" = \"test\" ]; then
24793            echo \"foo bar\"
24794        else
24795            echo \"bar baz\"
24796            ˇ
24797    "});
24798    cx.update_editor(|editor, window, cx| {
24799        editor.handle_input("fi", window, cx);
24800    });
24801    cx.assert_editor_state(indoc! {"
24802        if [ \"$1\" = \"test\" ]; then
24803            echo \"foo bar\"
24804        else
24805            echo \"bar baz\"
24806        fiˇ
24807    "});
24808
24809    // test `done` auto outdents when typed inside `while` block
24810    cx.set_state(indoc! {"
24811        while read line; do
24812            echo \"$line\"
24813            ˇ
24814    "});
24815    cx.update_editor(|editor, window, cx| {
24816        editor.handle_input("done", window, cx);
24817    });
24818    cx.assert_editor_state(indoc! {"
24819        while read line; do
24820            echo \"$line\"
24821        doneˇ
24822    "});
24823
24824    // test `done` auto outdents when typed inside `for` block
24825    cx.set_state(indoc! {"
24826        for file in *.txt; do
24827            cat \"$file\"
24828            ˇ
24829    "});
24830    cx.update_editor(|editor, window, cx| {
24831        editor.handle_input("done", window, cx);
24832    });
24833    cx.assert_editor_state(indoc! {"
24834        for file in *.txt; do
24835            cat \"$file\"
24836        doneˇ
24837    "});
24838
24839    // test `esac` auto outdents when typed inside `case` block
24840    cx.set_state(indoc! {"
24841        case \"$1\" in
24842            start)
24843                echo \"foo bar\"
24844                ;;
24845            stop)
24846                echo \"bar baz\"
24847                ;;
24848            ˇ
24849    "});
24850    cx.update_editor(|editor, window, cx| {
24851        editor.handle_input("esac", window, cx);
24852    });
24853    cx.assert_editor_state(indoc! {"
24854        case \"$1\" in
24855            start)
24856                echo \"foo bar\"
24857                ;;
24858            stop)
24859                echo \"bar baz\"
24860                ;;
24861        esacˇ
24862    "});
24863
24864    // test `*)` auto outdents when typed inside `case` block
24865    cx.set_state(indoc! {"
24866        case \"$1\" in
24867            start)
24868                echo \"foo bar\"
24869                ;;
24870                ˇ
24871    "});
24872    cx.update_editor(|editor, window, cx| {
24873        editor.handle_input("*)", window, cx);
24874    });
24875    cx.assert_editor_state(indoc! {"
24876        case \"$1\" in
24877            start)
24878                echo \"foo bar\"
24879                ;;
24880            *)ˇ
24881    "});
24882
24883    // test `fi` outdents to correct level with nested if blocks
24884    cx.set_state(indoc! {"
24885        if [ \"$1\" = \"test\" ]; then
24886            echo \"outer if\"
24887            if [ \"$2\" = \"debug\" ]; then
24888                echo \"inner if\"
24889                ˇ
24890    "});
24891    cx.update_editor(|editor, window, cx| {
24892        editor.handle_input("fi", window, cx);
24893    });
24894    cx.assert_editor_state(indoc! {"
24895        if [ \"$1\" = \"test\" ]; then
24896            echo \"outer if\"
24897            if [ \"$2\" = \"debug\" ]; then
24898                echo \"inner if\"
24899            fiˇ
24900    "});
24901}
24902
24903#[gpui::test]
24904async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
24905    init_test(cx, |_| {});
24906    update_test_language_settings(cx, |settings| {
24907        settings.defaults.extend_comment_on_newline = Some(false);
24908    });
24909    let mut cx = EditorTestContext::new(cx).await;
24910    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24911    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24912
24913    // test correct indent after newline on comment
24914    cx.set_state(indoc! {"
24915        # COMMENT:ˇ
24916    "});
24917    cx.update_editor(|editor, window, cx| {
24918        editor.newline(&Newline, window, cx);
24919    });
24920    cx.assert_editor_state(indoc! {"
24921        # COMMENT:
24922        ˇ
24923    "});
24924
24925    // test correct indent after newline after `then`
24926    cx.set_state(indoc! {"
24927
24928        if [ \"$1\" = \"test\" ]; thenˇ
24929    "});
24930    cx.update_editor(|editor, window, cx| {
24931        editor.newline(&Newline, window, cx);
24932    });
24933    cx.run_until_parked();
24934    cx.assert_editor_state(indoc! {"
24935
24936        if [ \"$1\" = \"test\" ]; then
24937            ˇ
24938    "});
24939
24940    // test correct indent after newline after `else`
24941    cx.set_state(indoc! {"
24942        if [ \"$1\" = \"test\" ]; then
24943        elseˇ
24944    "});
24945    cx.update_editor(|editor, window, cx| {
24946        editor.newline(&Newline, window, cx);
24947    });
24948    cx.run_until_parked();
24949    cx.assert_editor_state(indoc! {"
24950        if [ \"$1\" = \"test\" ]; then
24951        else
24952            ˇ
24953    "});
24954
24955    // test correct indent after newline after `elif`
24956    cx.set_state(indoc! {"
24957        if [ \"$1\" = \"test\" ]; then
24958        elifˇ
24959    "});
24960    cx.update_editor(|editor, window, cx| {
24961        editor.newline(&Newline, window, cx);
24962    });
24963    cx.run_until_parked();
24964    cx.assert_editor_state(indoc! {"
24965        if [ \"$1\" = \"test\" ]; then
24966        elif
24967            ˇ
24968    "});
24969
24970    // test correct indent after newline after `do`
24971    cx.set_state(indoc! {"
24972        for file in *.txt; doˇ
24973    "});
24974    cx.update_editor(|editor, window, cx| {
24975        editor.newline(&Newline, window, cx);
24976    });
24977    cx.run_until_parked();
24978    cx.assert_editor_state(indoc! {"
24979        for file in *.txt; do
24980            ˇ
24981    "});
24982
24983    // test correct indent after newline after case pattern
24984    cx.set_state(indoc! {"
24985        case \"$1\" in
24986            start)ˇ
24987    "});
24988    cx.update_editor(|editor, window, cx| {
24989        editor.newline(&Newline, window, cx);
24990    });
24991    cx.run_until_parked();
24992    cx.assert_editor_state(indoc! {"
24993        case \"$1\" in
24994            start)
24995                ˇ
24996    "});
24997
24998    // test correct indent after newline after case pattern
24999    cx.set_state(indoc! {"
25000        case \"$1\" in
25001            start)
25002                ;;
25003            *)ˇ
25004    "});
25005    cx.update_editor(|editor, window, cx| {
25006        editor.newline(&Newline, window, cx);
25007    });
25008    cx.run_until_parked();
25009    cx.assert_editor_state(indoc! {"
25010        case \"$1\" in
25011            start)
25012                ;;
25013            *)
25014                ˇ
25015    "});
25016
25017    // test correct indent after newline after function opening brace
25018    cx.set_state(indoc! {"
25019        function test() {ˇ}
25020    "});
25021    cx.update_editor(|editor, window, cx| {
25022        editor.newline(&Newline, window, cx);
25023    });
25024    cx.run_until_parked();
25025    cx.assert_editor_state(indoc! {"
25026        function test() {
25027            ˇ
25028        }
25029    "});
25030
25031    // test no extra indent after semicolon on same line
25032    cx.set_state(indoc! {"
25033        echo \"test\"25034    "});
25035    cx.update_editor(|editor, window, cx| {
25036        editor.newline(&Newline, window, cx);
25037    });
25038    cx.run_until_parked();
25039    cx.assert_editor_state(indoc! {"
25040        echo \"test\";
25041        ˇ
25042    "});
25043}
25044
25045fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
25046    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
25047    point..point
25048}
25049
25050#[track_caller]
25051fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
25052    let (text, ranges) = marked_text_ranges(marked_text, true);
25053    assert_eq!(editor.text(cx), text);
25054    assert_eq!(
25055        editor.selections.ranges(cx),
25056        ranges,
25057        "Assert selections are {}",
25058        marked_text
25059    );
25060}
25061
25062pub fn handle_signature_help_request(
25063    cx: &mut EditorLspTestContext,
25064    mocked_response: lsp::SignatureHelp,
25065) -> impl Future<Output = ()> + use<> {
25066    let mut request =
25067        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
25068            let mocked_response = mocked_response.clone();
25069            async move { Ok(Some(mocked_response)) }
25070        });
25071
25072    async move {
25073        request.next().await;
25074    }
25075}
25076
25077#[track_caller]
25078pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
25079    cx.update_editor(|editor, _, _| {
25080        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
25081            let entries = menu.entries.borrow();
25082            let entries = entries
25083                .iter()
25084                .map(|entry| entry.string.as_str())
25085                .collect::<Vec<_>>();
25086            assert_eq!(entries, expected);
25087        } else {
25088            panic!("Expected completions menu");
25089        }
25090    });
25091}
25092
25093/// Handle completion request passing a marked string specifying where the completion
25094/// should be triggered from using '|' character, what range should be replaced, and what completions
25095/// should be returned using '<' and '>' to delimit the range.
25096///
25097/// Also see `handle_completion_request_with_insert_and_replace`.
25098#[track_caller]
25099pub fn handle_completion_request(
25100    marked_string: &str,
25101    completions: Vec<&'static str>,
25102    is_incomplete: bool,
25103    counter: Arc<AtomicUsize>,
25104    cx: &mut EditorLspTestContext,
25105) -> impl Future<Output = ()> {
25106    let complete_from_marker: TextRangeMarker = '|'.into();
25107    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25108    let (_, mut marked_ranges) = marked_text_ranges_by(
25109        marked_string,
25110        vec![complete_from_marker.clone(), replace_range_marker.clone()],
25111    );
25112
25113    let complete_from_position =
25114        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25115    let replace_range =
25116        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25117
25118    let mut request =
25119        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25120            let completions = completions.clone();
25121            counter.fetch_add(1, atomic::Ordering::Release);
25122            async move {
25123                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25124                assert_eq!(
25125                    params.text_document_position.position,
25126                    complete_from_position
25127                );
25128                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
25129                    is_incomplete,
25130                    item_defaults: None,
25131                    items: completions
25132                        .iter()
25133                        .map(|completion_text| lsp::CompletionItem {
25134                            label: completion_text.to_string(),
25135                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
25136                                range: replace_range,
25137                                new_text: completion_text.to_string(),
25138                            })),
25139                            ..Default::default()
25140                        })
25141                        .collect(),
25142                })))
25143            }
25144        });
25145
25146    async move {
25147        request.next().await;
25148    }
25149}
25150
25151/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
25152/// given instead, which also contains an `insert` range.
25153///
25154/// This function uses markers to define ranges:
25155/// - `|` marks the cursor position
25156/// - `<>` marks the replace range
25157/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
25158pub fn handle_completion_request_with_insert_and_replace(
25159    cx: &mut EditorLspTestContext,
25160    marked_string: &str,
25161    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
25162    counter: Arc<AtomicUsize>,
25163) -> impl Future<Output = ()> {
25164    let complete_from_marker: TextRangeMarker = '|'.into();
25165    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25166    let insert_range_marker: TextRangeMarker = ('{', '}').into();
25167
25168    let (_, mut marked_ranges) = marked_text_ranges_by(
25169        marked_string,
25170        vec![
25171            complete_from_marker.clone(),
25172            replace_range_marker.clone(),
25173            insert_range_marker.clone(),
25174        ],
25175    );
25176
25177    let complete_from_position =
25178        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25179    let replace_range =
25180        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25181
25182    let insert_range = match marked_ranges.remove(&insert_range_marker) {
25183        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
25184        _ => lsp::Range {
25185            start: replace_range.start,
25186            end: complete_from_position,
25187        },
25188    };
25189
25190    let mut request =
25191        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25192            let completions = completions.clone();
25193            counter.fetch_add(1, atomic::Ordering::Release);
25194            async move {
25195                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25196                assert_eq!(
25197                    params.text_document_position.position, complete_from_position,
25198                    "marker `|` position doesn't match",
25199                );
25200                Ok(Some(lsp::CompletionResponse::Array(
25201                    completions
25202                        .iter()
25203                        .map(|(label, new_text)| lsp::CompletionItem {
25204                            label: label.to_string(),
25205                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25206                                lsp::InsertReplaceEdit {
25207                                    insert: insert_range,
25208                                    replace: replace_range,
25209                                    new_text: new_text.to_string(),
25210                                },
25211                            )),
25212                            ..Default::default()
25213                        })
25214                        .collect(),
25215                )))
25216            }
25217        });
25218
25219    async move {
25220        request.next().await;
25221    }
25222}
25223
25224fn handle_resolve_completion_request(
25225    cx: &mut EditorLspTestContext,
25226    edits: Option<Vec<(&'static str, &'static str)>>,
25227) -> impl Future<Output = ()> {
25228    let edits = edits.map(|edits| {
25229        edits
25230            .iter()
25231            .map(|(marked_string, new_text)| {
25232                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
25233                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
25234                lsp::TextEdit::new(replace_range, new_text.to_string())
25235            })
25236            .collect::<Vec<_>>()
25237    });
25238
25239    let mut request =
25240        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
25241            let edits = edits.clone();
25242            async move {
25243                Ok(lsp::CompletionItem {
25244                    additional_text_edits: edits,
25245                    ..Default::default()
25246                })
25247            }
25248        });
25249
25250    async move {
25251        request.next().await;
25252    }
25253}
25254
25255pub(crate) fn update_test_language_settings(
25256    cx: &mut TestAppContext,
25257    f: impl Fn(&mut AllLanguageSettingsContent),
25258) {
25259    cx.update(|cx| {
25260        SettingsStore::update_global(cx, |store, cx| {
25261            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
25262        });
25263    });
25264}
25265
25266pub(crate) fn update_test_project_settings(
25267    cx: &mut TestAppContext,
25268    f: impl Fn(&mut ProjectSettingsContent),
25269) {
25270    cx.update(|cx| {
25271        SettingsStore::update_global(cx, |store, cx| {
25272            store.update_user_settings(cx, |settings| f(&mut settings.project));
25273        });
25274    });
25275}
25276
25277pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
25278    cx.update(|cx| {
25279        assets::Assets.load_test_fonts(cx);
25280        let store = SettingsStore::test(cx);
25281        cx.set_global(store);
25282        theme::init(theme::LoadThemes::JustBase, cx);
25283        release_channel::init(SemanticVersion::default(), cx);
25284        client::init_settings(cx);
25285        language::init(cx);
25286        Project::init_settings(cx);
25287        workspace::init_settings(cx);
25288        crate::init(cx);
25289    });
25290    zlog::init_test();
25291    update_test_language_settings(cx, f);
25292}
25293
25294#[track_caller]
25295fn assert_hunk_revert(
25296    not_reverted_text_with_selections: &str,
25297    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
25298    expected_reverted_text_with_selections: &str,
25299    base_text: &str,
25300    cx: &mut EditorLspTestContext,
25301) {
25302    cx.set_state(not_reverted_text_with_selections);
25303    cx.set_head_text(base_text);
25304    cx.executor().run_until_parked();
25305
25306    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
25307        let snapshot = editor.snapshot(window, cx);
25308        let reverted_hunk_statuses = snapshot
25309            .buffer_snapshot
25310            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
25311            .map(|hunk| hunk.status().kind)
25312            .collect::<Vec<_>>();
25313
25314        editor.git_restore(&Default::default(), window, cx);
25315        reverted_hunk_statuses
25316    });
25317    cx.executor().run_until_parked();
25318    cx.assert_editor_state(expected_reverted_text_with_selections);
25319    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
25320}
25321
25322#[gpui::test(iterations = 10)]
25323async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
25324    init_test(cx, |_| {});
25325
25326    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
25327    let counter = diagnostic_requests.clone();
25328
25329    let fs = FakeFs::new(cx.executor());
25330    fs.insert_tree(
25331        path!("/a"),
25332        json!({
25333            "first.rs": "fn main() { let a = 5; }",
25334            "second.rs": "// Test file",
25335        }),
25336    )
25337    .await;
25338
25339    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25340    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25341    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25342
25343    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25344    language_registry.add(rust_lang());
25345    let mut fake_servers = language_registry.register_fake_lsp(
25346        "Rust",
25347        FakeLspAdapter {
25348            capabilities: lsp::ServerCapabilities {
25349                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
25350                    lsp::DiagnosticOptions {
25351                        identifier: None,
25352                        inter_file_dependencies: true,
25353                        workspace_diagnostics: true,
25354                        work_done_progress_options: Default::default(),
25355                    },
25356                )),
25357                ..Default::default()
25358            },
25359            ..Default::default()
25360        },
25361    );
25362
25363    let editor = workspace
25364        .update(cx, |workspace, window, cx| {
25365            workspace.open_abs_path(
25366                PathBuf::from(path!("/a/first.rs")),
25367                OpenOptions::default(),
25368                window,
25369                cx,
25370            )
25371        })
25372        .unwrap()
25373        .await
25374        .unwrap()
25375        .downcast::<Editor>()
25376        .unwrap();
25377    let fake_server = fake_servers.next().await.unwrap();
25378    let server_id = fake_server.server.server_id();
25379    let mut first_request = fake_server
25380        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
25381            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
25382            let result_id = Some(new_result_id.to_string());
25383            assert_eq!(
25384                params.text_document.uri,
25385                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25386            );
25387            async move {
25388                Ok(lsp::DocumentDiagnosticReportResult::Report(
25389                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
25390                        related_documents: None,
25391                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
25392                            items: Vec::new(),
25393                            result_id,
25394                        },
25395                    }),
25396                ))
25397            }
25398        });
25399
25400    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
25401        project.update(cx, |project, cx| {
25402            let buffer_id = editor
25403                .read(cx)
25404                .buffer()
25405                .read(cx)
25406                .as_singleton()
25407                .expect("created a singleton buffer")
25408                .read(cx)
25409                .remote_id();
25410            let buffer_result_id = project
25411                .lsp_store()
25412                .read(cx)
25413                .result_id(server_id, buffer_id, cx);
25414            assert_eq!(expected, buffer_result_id);
25415        });
25416    };
25417
25418    ensure_result_id(None, cx);
25419    cx.executor().advance_clock(Duration::from_millis(60));
25420    cx.executor().run_until_parked();
25421    assert_eq!(
25422        diagnostic_requests.load(atomic::Ordering::Acquire),
25423        1,
25424        "Opening file should trigger diagnostic request"
25425    );
25426    first_request
25427        .next()
25428        .await
25429        .expect("should have sent the first diagnostics pull request");
25430    ensure_result_id(Some("1".to_string()), cx);
25431
25432    // Editing should trigger diagnostics
25433    editor.update_in(cx, |editor, window, cx| {
25434        editor.handle_input("2", window, cx)
25435    });
25436    cx.executor().advance_clock(Duration::from_millis(60));
25437    cx.executor().run_until_parked();
25438    assert_eq!(
25439        diagnostic_requests.load(atomic::Ordering::Acquire),
25440        2,
25441        "Editing should trigger diagnostic request"
25442    );
25443    ensure_result_id(Some("2".to_string()), cx);
25444
25445    // Moving cursor should not trigger diagnostic request
25446    editor.update_in(cx, |editor, window, cx| {
25447        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25448            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
25449        });
25450    });
25451    cx.executor().advance_clock(Duration::from_millis(60));
25452    cx.executor().run_until_parked();
25453    assert_eq!(
25454        diagnostic_requests.load(atomic::Ordering::Acquire),
25455        2,
25456        "Cursor movement should not trigger diagnostic request"
25457    );
25458    ensure_result_id(Some("2".to_string()), cx);
25459    // Multiple rapid edits should be debounced
25460    for _ in 0..5 {
25461        editor.update_in(cx, |editor, window, cx| {
25462            editor.handle_input("x", window, cx)
25463        });
25464    }
25465    cx.executor().advance_clock(Duration::from_millis(60));
25466    cx.executor().run_until_parked();
25467
25468    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
25469    assert!(
25470        final_requests <= 4,
25471        "Multiple rapid edits should be debounced (got {final_requests} requests)",
25472    );
25473    ensure_result_id(Some(final_requests.to_string()), cx);
25474}
25475
25476#[gpui::test]
25477async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
25478    // Regression test for issue #11671
25479    // Previously, adding a cursor after moving multiple cursors would reset
25480    // the cursor count instead of adding to the existing cursors.
25481    init_test(cx, |_| {});
25482    let mut cx = EditorTestContext::new(cx).await;
25483
25484    // Create a simple buffer with cursor at start
25485    cx.set_state(indoc! {"
25486        ˇaaaa
25487        bbbb
25488        cccc
25489        dddd
25490        eeee
25491        ffff
25492        gggg
25493        hhhh"});
25494
25495    // Add 2 cursors below (so we have 3 total)
25496    cx.update_editor(|editor, window, cx| {
25497        editor.add_selection_below(&Default::default(), window, cx);
25498        editor.add_selection_below(&Default::default(), window, cx);
25499    });
25500
25501    // Verify we have 3 cursors
25502    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
25503    assert_eq!(
25504        initial_count, 3,
25505        "Should have 3 cursors after adding 2 below"
25506    );
25507
25508    // Move down one line
25509    cx.update_editor(|editor, window, cx| {
25510        editor.move_down(&MoveDown, window, cx);
25511    });
25512
25513    // Add another cursor below
25514    cx.update_editor(|editor, window, cx| {
25515        editor.add_selection_below(&Default::default(), window, cx);
25516    });
25517
25518    // Should now have 4 cursors (3 original + 1 new)
25519    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
25520    assert_eq!(
25521        final_count, 4,
25522        "Should have 4 cursors after moving and adding another"
25523    );
25524}
25525
25526#[gpui::test(iterations = 10)]
25527async fn test_document_colors(cx: &mut TestAppContext) {
25528    let expected_color = Rgba {
25529        r: 0.33,
25530        g: 0.33,
25531        b: 0.33,
25532        a: 0.33,
25533    };
25534
25535    init_test(cx, |_| {});
25536
25537    let fs = FakeFs::new(cx.executor());
25538    fs.insert_tree(
25539        path!("/a"),
25540        json!({
25541            "first.rs": "fn main() { let a = 5; }",
25542        }),
25543    )
25544    .await;
25545
25546    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25547    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25548    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25549
25550    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25551    language_registry.add(rust_lang());
25552    let mut fake_servers = language_registry.register_fake_lsp(
25553        "Rust",
25554        FakeLspAdapter {
25555            capabilities: lsp::ServerCapabilities {
25556                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
25557                ..lsp::ServerCapabilities::default()
25558            },
25559            name: "rust-analyzer",
25560            ..FakeLspAdapter::default()
25561        },
25562    );
25563    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
25564        "Rust",
25565        FakeLspAdapter {
25566            capabilities: lsp::ServerCapabilities {
25567                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
25568                ..lsp::ServerCapabilities::default()
25569            },
25570            name: "not-rust-analyzer",
25571            ..FakeLspAdapter::default()
25572        },
25573    );
25574
25575    let editor = workspace
25576        .update(cx, |workspace, window, cx| {
25577            workspace.open_abs_path(
25578                PathBuf::from(path!("/a/first.rs")),
25579                OpenOptions::default(),
25580                window,
25581                cx,
25582            )
25583        })
25584        .unwrap()
25585        .await
25586        .unwrap()
25587        .downcast::<Editor>()
25588        .unwrap();
25589    let fake_language_server = fake_servers.next().await.unwrap();
25590    let fake_language_server_without_capabilities =
25591        fake_servers_without_capabilities.next().await.unwrap();
25592    let requests_made = Arc::new(AtomicUsize::new(0));
25593    let closure_requests_made = Arc::clone(&requests_made);
25594    let mut color_request_handle = fake_language_server
25595        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25596            let requests_made = Arc::clone(&closure_requests_made);
25597            async move {
25598                assert_eq!(
25599                    params.text_document.uri,
25600                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25601                );
25602                requests_made.fetch_add(1, atomic::Ordering::Release);
25603                Ok(vec![
25604                    lsp::ColorInformation {
25605                        range: lsp::Range {
25606                            start: lsp::Position {
25607                                line: 0,
25608                                character: 0,
25609                            },
25610                            end: lsp::Position {
25611                                line: 0,
25612                                character: 1,
25613                            },
25614                        },
25615                        color: lsp::Color {
25616                            red: 0.33,
25617                            green: 0.33,
25618                            blue: 0.33,
25619                            alpha: 0.33,
25620                        },
25621                    },
25622                    lsp::ColorInformation {
25623                        range: lsp::Range {
25624                            start: lsp::Position {
25625                                line: 0,
25626                                character: 0,
25627                            },
25628                            end: lsp::Position {
25629                                line: 0,
25630                                character: 1,
25631                            },
25632                        },
25633                        color: lsp::Color {
25634                            red: 0.33,
25635                            green: 0.33,
25636                            blue: 0.33,
25637                            alpha: 0.33,
25638                        },
25639                    },
25640                ])
25641            }
25642        });
25643
25644    let _handle = fake_language_server_without_capabilities
25645        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
25646            panic!("Should not be called");
25647        });
25648    cx.executor().advance_clock(Duration::from_millis(100));
25649    color_request_handle.next().await.unwrap();
25650    cx.run_until_parked();
25651    assert_eq!(
25652        1,
25653        requests_made.load(atomic::Ordering::Acquire),
25654        "Should query for colors once per editor open"
25655    );
25656    editor.update_in(cx, |editor, _, cx| {
25657        assert_eq!(
25658            vec![expected_color],
25659            extract_color_inlays(editor, cx),
25660            "Should have an initial inlay"
25661        );
25662    });
25663
25664    // opening another file in a split should not influence the LSP query counter
25665    workspace
25666        .update(cx, |workspace, window, cx| {
25667            assert_eq!(
25668                workspace.panes().len(),
25669                1,
25670                "Should have one pane with one editor"
25671            );
25672            workspace.move_item_to_pane_in_direction(
25673                &MoveItemToPaneInDirection {
25674                    direction: SplitDirection::Right,
25675                    focus: false,
25676                    clone: true,
25677                },
25678                window,
25679                cx,
25680            );
25681        })
25682        .unwrap();
25683    cx.run_until_parked();
25684    workspace
25685        .update(cx, |workspace, _, cx| {
25686            let panes = workspace.panes();
25687            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
25688            for pane in panes {
25689                let editor = pane
25690                    .read(cx)
25691                    .active_item()
25692                    .and_then(|item| item.downcast::<Editor>())
25693                    .expect("Should have opened an editor in each split");
25694                let editor_file = editor
25695                    .read(cx)
25696                    .buffer()
25697                    .read(cx)
25698                    .as_singleton()
25699                    .expect("test deals with singleton buffers")
25700                    .read(cx)
25701                    .file()
25702                    .expect("test buffese should have a file")
25703                    .path();
25704                assert_eq!(
25705                    editor_file.as_ref(),
25706                    rel_path("first.rs"),
25707                    "Both editors should be opened for the same file"
25708                )
25709            }
25710        })
25711        .unwrap();
25712
25713    cx.executor().advance_clock(Duration::from_millis(500));
25714    let save = editor.update_in(cx, |editor, window, cx| {
25715        editor.move_to_end(&MoveToEnd, window, cx);
25716        editor.handle_input("dirty", window, cx);
25717        editor.save(
25718            SaveOptions {
25719                format: true,
25720                autosave: true,
25721            },
25722            project.clone(),
25723            window,
25724            cx,
25725        )
25726    });
25727    save.await.unwrap();
25728
25729    color_request_handle.next().await.unwrap();
25730    cx.run_until_parked();
25731    assert_eq!(
25732        3,
25733        requests_made.load(atomic::Ordering::Acquire),
25734        "Should query for colors once per save and once per formatting after save"
25735    );
25736
25737    drop(editor);
25738    let close = workspace
25739        .update(cx, |workspace, window, cx| {
25740            workspace.active_pane().update(cx, |pane, cx| {
25741                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25742            })
25743        })
25744        .unwrap();
25745    close.await.unwrap();
25746    let close = workspace
25747        .update(cx, |workspace, window, cx| {
25748            workspace.active_pane().update(cx, |pane, cx| {
25749                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25750            })
25751        })
25752        .unwrap();
25753    close.await.unwrap();
25754    assert_eq!(
25755        3,
25756        requests_made.load(atomic::Ordering::Acquire),
25757        "After saving and closing all editors, no extra requests should be made"
25758    );
25759    workspace
25760        .update(cx, |workspace, _, cx| {
25761            assert!(
25762                workspace.active_item(cx).is_none(),
25763                "Should close all editors"
25764            )
25765        })
25766        .unwrap();
25767
25768    workspace
25769        .update(cx, |workspace, window, cx| {
25770            workspace.active_pane().update(cx, |pane, cx| {
25771                pane.navigate_backward(&workspace::GoBack, window, cx);
25772            })
25773        })
25774        .unwrap();
25775    cx.executor().advance_clock(Duration::from_millis(100));
25776    cx.run_until_parked();
25777    let editor = workspace
25778        .update(cx, |workspace, _, cx| {
25779            workspace
25780                .active_item(cx)
25781                .expect("Should have reopened the editor again after navigating back")
25782                .downcast::<Editor>()
25783                .expect("Should be an editor")
25784        })
25785        .unwrap();
25786    color_request_handle.next().await.unwrap();
25787    assert_eq!(
25788        3,
25789        requests_made.load(atomic::Ordering::Acquire),
25790        "Cache should be reused on buffer close and reopen"
25791    );
25792    editor.update(cx, |editor, cx| {
25793        assert_eq!(
25794            vec![expected_color],
25795            extract_color_inlays(editor, cx),
25796            "Should have an initial inlay"
25797        );
25798    });
25799
25800    drop(color_request_handle);
25801    let closure_requests_made = Arc::clone(&requests_made);
25802    let mut empty_color_request_handle = fake_language_server
25803        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25804            let requests_made = Arc::clone(&closure_requests_made);
25805            async move {
25806                assert_eq!(
25807                    params.text_document.uri,
25808                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25809                );
25810                requests_made.fetch_add(1, atomic::Ordering::Release);
25811                Ok(Vec::new())
25812            }
25813        });
25814    let save = editor.update_in(cx, |editor, window, cx| {
25815        editor.move_to_end(&MoveToEnd, window, cx);
25816        editor.handle_input("dirty_again", window, cx);
25817        editor.save(
25818            SaveOptions {
25819                format: false,
25820                autosave: true,
25821            },
25822            project.clone(),
25823            window,
25824            cx,
25825        )
25826    });
25827    save.await.unwrap();
25828
25829    empty_color_request_handle.next().await.unwrap();
25830    cx.run_until_parked();
25831    assert_eq!(
25832        4,
25833        requests_made.load(atomic::Ordering::Acquire),
25834        "Should query for colors once per save only, as formatting was not requested"
25835    );
25836    editor.update(cx, |editor, cx| {
25837        assert_eq!(
25838            Vec::<Rgba>::new(),
25839            extract_color_inlays(editor, cx),
25840            "Should clear all colors when the server returns an empty response"
25841        );
25842    });
25843}
25844
25845#[gpui::test]
25846async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
25847    init_test(cx, |_| {});
25848    let (editor, cx) = cx.add_window_view(Editor::single_line);
25849    editor.update_in(cx, |editor, window, cx| {
25850        editor.set_text("oops\n\nwow\n", window, cx)
25851    });
25852    cx.run_until_parked();
25853    editor.update(cx, |editor, cx| {
25854        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
25855    });
25856    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
25857    cx.run_until_parked();
25858    editor.update(cx, |editor, cx| {
25859        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
25860    });
25861}
25862
25863#[gpui::test]
25864async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
25865    init_test(cx, |_| {});
25866
25867    cx.update(|cx| {
25868        register_project_item::<Editor>(cx);
25869    });
25870
25871    let fs = FakeFs::new(cx.executor());
25872    fs.insert_tree("/root1", json!({})).await;
25873    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
25874        .await;
25875
25876    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
25877    let (workspace, cx) =
25878        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25879
25880    let worktree_id = project.update(cx, |project, cx| {
25881        project.worktrees(cx).next().unwrap().read(cx).id()
25882    });
25883
25884    let handle = workspace
25885        .update_in(cx, |workspace, window, cx| {
25886            let project_path = (worktree_id, rel_path("one.pdf"));
25887            workspace.open_path(project_path, None, true, window, cx)
25888        })
25889        .await
25890        .unwrap();
25891
25892    assert_eq!(
25893        handle.to_any().entity_type(),
25894        TypeId::of::<InvalidBufferView>()
25895    );
25896}
25897
25898#[gpui::test]
25899async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
25900    init_test(cx, |_| {});
25901
25902    let language = Arc::new(Language::new(
25903        LanguageConfig::default(),
25904        Some(tree_sitter_rust::LANGUAGE.into()),
25905    ));
25906
25907    // Test hierarchical sibling navigation
25908    let text = r#"
25909        fn outer() {
25910            if condition {
25911                let a = 1;
25912            }
25913            let b = 2;
25914        }
25915
25916        fn another() {
25917            let c = 3;
25918        }
25919    "#;
25920
25921    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
25922    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25923    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
25924
25925    // Wait for parsing to complete
25926    editor
25927        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
25928        .await;
25929
25930    editor.update_in(cx, |editor, window, cx| {
25931        // Start by selecting "let a = 1;" inside the if block
25932        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25933            s.select_display_ranges([
25934                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
25935            ]);
25936        });
25937
25938        let initial_selection = editor.selections.display_ranges(cx);
25939        assert_eq!(initial_selection.len(), 1, "Should have one selection");
25940
25941        // Test select next sibling - should move up levels to find the next sibling
25942        // Since "let a = 1;" has no siblings in the if block, it should move up
25943        // to find "let b = 2;" which is a sibling of the if block
25944        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
25945        let next_selection = editor.selections.display_ranges(cx);
25946
25947        // Should have a selection and it should be different from the initial
25948        assert_eq!(
25949            next_selection.len(),
25950            1,
25951            "Should have one selection after next"
25952        );
25953        assert_ne!(
25954            next_selection[0], initial_selection[0],
25955            "Next sibling selection should be different"
25956        );
25957
25958        // Test hierarchical navigation by going to the end of the current function
25959        // and trying to navigate to the next function
25960        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25961            s.select_display_ranges([
25962                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
25963            ]);
25964        });
25965
25966        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
25967        let function_next_selection = editor.selections.display_ranges(cx);
25968
25969        // Should move to the next function
25970        assert_eq!(
25971            function_next_selection.len(),
25972            1,
25973            "Should have one selection after function next"
25974        );
25975
25976        // Test select previous sibling navigation
25977        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
25978        let prev_selection = editor.selections.display_ranges(cx);
25979
25980        // Should have a selection and it should be different
25981        assert_eq!(
25982            prev_selection.len(),
25983            1,
25984            "Should have one selection after prev"
25985        );
25986        assert_ne!(
25987            prev_selection[0], function_next_selection[0],
25988            "Previous sibling selection should be different from next"
25989        );
25990    });
25991}
25992
25993#[gpui::test]
25994async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
25995    init_test(cx, |_| {});
25996
25997    let mut cx = EditorTestContext::new(cx).await;
25998    cx.set_state(
25999        "let ˇvariable = 42;
26000let another = variable + 1;
26001let result = variable * 2;",
26002    );
26003
26004    // Set up document highlights manually (simulating LSP response)
26005    cx.update_editor(|editor, _window, cx| {
26006        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
26007
26008        // Create highlights for "variable" occurrences
26009        let highlight_ranges = [
26010            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
26011            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
26012            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
26013        ];
26014
26015        let anchor_ranges: Vec<_> = highlight_ranges
26016            .iter()
26017            .map(|range| range.clone().to_anchors(&buffer_snapshot))
26018            .collect();
26019
26020        editor.highlight_background::<DocumentHighlightRead>(
26021            &anchor_ranges,
26022            |theme| theme.colors().editor_document_highlight_read_background,
26023            cx,
26024        );
26025    });
26026
26027    // Go to next highlight - should move to second "variable"
26028    cx.update_editor(|editor, window, cx| {
26029        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26030    });
26031    cx.assert_editor_state(
26032        "let variable = 42;
26033let another = ˇvariable + 1;
26034let result = variable * 2;",
26035    );
26036
26037    // Go to next highlight - should move to third "variable"
26038    cx.update_editor(|editor, window, cx| {
26039        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26040    });
26041    cx.assert_editor_state(
26042        "let variable = 42;
26043let another = variable + 1;
26044let result = ˇvariable * 2;",
26045    );
26046
26047    // Go to next highlight - should stay at third "variable" (no wrap-around)
26048    cx.update_editor(|editor, window, cx| {
26049        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26050    });
26051    cx.assert_editor_state(
26052        "let variable = 42;
26053let another = variable + 1;
26054let result = ˇvariable * 2;",
26055    );
26056
26057    // Now test going backwards from third position
26058    cx.update_editor(|editor, window, cx| {
26059        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26060    });
26061    cx.assert_editor_state(
26062        "let variable = 42;
26063let another = ˇvariable + 1;
26064let result = variable * 2;",
26065    );
26066
26067    // Go to previous highlight - should move to first "variable"
26068    cx.update_editor(|editor, window, cx| {
26069        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26070    });
26071    cx.assert_editor_state(
26072        "let ˇvariable = 42;
26073let another = variable + 1;
26074let result = variable * 2;",
26075    );
26076
26077    // Go to previous highlight - should stay on first "variable"
26078    cx.update_editor(|editor, window, cx| {
26079        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26080    });
26081    cx.assert_editor_state(
26082        "let ˇvariable = 42;
26083let another = variable + 1;
26084let result = variable * 2;",
26085    );
26086}
26087
26088#[gpui::test]
26089async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
26090    cx: &mut gpui::TestAppContext,
26091) {
26092    init_test(cx, |_| {});
26093
26094    let url = "https://zed.dev";
26095
26096    let markdown_language = Arc::new(Language::new(
26097        LanguageConfig {
26098            name: "Markdown".into(),
26099            ..LanguageConfig::default()
26100        },
26101        None,
26102    ));
26103
26104    let mut cx = EditorTestContext::new(cx).await;
26105    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26106    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
26107
26108    cx.update_editor(|editor, window, cx| {
26109        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26110        editor.paste(&Paste, window, cx);
26111    });
26112
26113    cx.assert_editor_state(&format!(
26114        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
26115    ));
26116}
26117
26118#[gpui::test]
26119async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
26120    cx: &mut gpui::TestAppContext,
26121) {
26122    init_test(cx, |_| {});
26123
26124    let url = "https://zed.dev";
26125
26126    let markdown_language = Arc::new(Language::new(
26127        LanguageConfig {
26128            name: "Markdown".into(),
26129            ..LanguageConfig::default()
26130        },
26131        None,
26132    ));
26133
26134    let mut cx = EditorTestContext::new(cx).await;
26135    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26136    cx.set_state(&format!(
26137        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
26138    ));
26139
26140    cx.update_editor(|editor, window, cx| {
26141        editor.copy(&Copy, window, cx);
26142    });
26143
26144    cx.set_state(&format!(
26145        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
26146    ));
26147
26148    cx.update_editor(|editor, window, cx| {
26149        editor.paste(&Paste, window, cx);
26150    });
26151
26152    cx.assert_editor_state(&format!(
26153        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
26154    ));
26155}
26156
26157#[gpui::test]
26158async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
26159    cx: &mut gpui::TestAppContext,
26160) {
26161    init_test(cx, |_| {});
26162
26163    let url = "https://zed.dev";
26164
26165    let markdown_language = Arc::new(Language::new(
26166        LanguageConfig {
26167            name: "Markdown".into(),
26168            ..LanguageConfig::default()
26169        },
26170        None,
26171    ));
26172
26173    let mut cx = EditorTestContext::new(cx).await;
26174    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26175    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
26176
26177    cx.update_editor(|editor, window, cx| {
26178        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26179        editor.paste(&Paste, window, cx);
26180    });
26181
26182    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
26183}
26184
26185#[gpui::test]
26186async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
26187    cx: &mut gpui::TestAppContext,
26188) {
26189    init_test(cx, |_| {});
26190
26191    let text = "Awesome";
26192
26193    let markdown_language = Arc::new(Language::new(
26194        LanguageConfig {
26195            name: "Markdown".into(),
26196            ..LanguageConfig::default()
26197        },
26198        None,
26199    ));
26200
26201    let mut cx = EditorTestContext::new(cx).await;
26202    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26203    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
26204
26205    cx.update_editor(|editor, window, cx| {
26206        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
26207        editor.paste(&Paste, window, cx);
26208    });
26209
26210    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
26211}
26212
26213#[gpui::test]
26214async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
26215    cx: &mut gpui::TestAppContext,
26216) {
26217    init_test(cx, |_| {});
26218
26219    let url = "https://zed.dev";
26220
26221    let markdown_language = Arc::new(Language::new(
26222        LanguageConfig {
26223            name: "Rust".into(),
26224            ..LanguageConfig::default()
26225        },
26226        None,
26227    ));
26228
26229    let mut cx = EditorTestContext::new(cx).await;
26230    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26231    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
26232
26233    cx.update_editor(|editor, window, cx| {
26234        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26235        editor.paste(&Paste, window, cx);
26236    });
26237
26238    cx.assert_editor_state(&format!(
26239        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
26240    ));
26241}
26242
26243#[gpui::test]
26244async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
26245    cx: &mut TestAppContext,
26246) {
26247    init_test(cx, |_| {});
26248
26249    let url = "https://zed.dev";
26250
26251    let markdown_language = Arc::new(Language::new(
26252        LanguageConfig {
26253            name: "Markdown".into(),
26254            ..LanguageConfig::default()
26255        },
26256        None,
26257    ));
26258
26259    let (editor, cx) = cx.add_window_view(|window, cx| {
26260        let multi_buffer = MultiBuffer::build_multi(
26261            [
26262                ("this will embed -> link", vec![Point::row_range(0..1)]),
26263                ("this will replace -> link", vec![Point::row_range(0..1)]),
26264            ],
26265            cx,
26266        );
26267        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
26268        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26269            s.select_ranges(vec![
26270                Point::new(0, 19)..Point::new(0, 23),
26271                Point::new(1, 21)..Point::new(1, 25),
26272            ])
26273        });
26274        let first_buffer_id = multi_buffer
26275            .read(cx)
26276            .excerpt_buffer_ids()
26277            .into_iter()
26278            .next()
26279            .unwrap();
26280        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
26281        first_buffer.update(cx, |buffer, cx| {
26282            buffer.set_language(Some(markdown_language.clone()), cx);
26283        });
26284
26285        editor
26286    });
26287    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
26288
26289    cx.update_editor(|editor, window, cx| {
26290        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26291        editor.paste(&Paste, window, cx);
26292    });
26293
26294    cx.assert_editor_state(&format!(
26295        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
26296    ));
26297}
26298
26299#[gpui::test]
26300async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
26301    init_test(cx, |_| {});
26302
26303    let fs = FakeFs::new(cx.executor());
26304    fs.insert_tree(
26305        path!("/project"),
26306        json!({
26307            "first.rs": "# First Document\nSome content here.",
26308            "second.rs": "Plain text content for second file.",
26309        }),
26310    )
26311    .await;
26312
26313    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
26314    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26315    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26316
26317    let language = rust_lang();
26318    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26319    language_registry.add(language.clone());
26320    let mut fake_servers = language_registry.register_fake_lsp(
26321        "Rust",
26322        FakeLspAdapter {
26323            ..FakeLspAdapter::default()
26324        },
26325    );
26326
26327    let buffer1 = project
26328        .update(cx, |project, cx| {
26329            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
26330        })
26331        .await
26332        .unwrap();
26333    let buffer2 = project
26334        .update(cx, |project, cx| {
26335            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
26336        })
26337        .await
26338        .unwrap();
26339
26340    let multi_buffer = cx.new(|cx| {
26341        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
26342        multi_buffer.set_excerpts_for_path(
26343            PathKey::for_buffer(&buffer1, cx),
26344            buffer1.clone(),
26345            [Point::zero()..buffer1.read(cx).max_point()],
26346            3,
26347            cx,
26348        );
26349        multi_buffer.set_excerpts_for_path(
26350            PathKey::for_buffer(&buffer2, cx),
26351            buffer2.clone(),
26352            [Point::zero()..buffer1.read(cx).max_point()],
26353            3,
26354            cx,
26355        );
26356        multi_buffer
26357    });
26358
26359    let (editor, cx) = cx.add_window_view(|window, cx| {
26360        Editor::new(
26361            EditorMode::full(),
26362            multi_buffer,
26363            Some(project.clone()),
26364            window,
26365            cx,
26366        )
26367    });
26368
26369    let fake_language_server = fake_servers.next().await.unwrap();
26370
26371    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
26372
26373    let save = editor.update_in(cx, |editor, window, cx| {
26374        assert!(editor.is_dirty(cx));
26375
26376        editor.save(
26377            SaveOptions {
26378                format: true,
26379                autosave: true,
26380            },
26381            project,
26382            window,
26383            cx,
26384        )
26385    });
26386    let (start_edit_tx, start_edit_rx) = oneshot::channel();
26387    let (done_edit_tx, done_edit_rx) = oneshot::channel();
26388    let mut done_edit_rx = Some(done_edit_rx);
26389    let mut start_edit_tx = Some(start_edit_tx);
26390
26391    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
26392        start_edit_tx.take().unwrap().send(()).unwrap();
26393        let done_edit_rx = done_edit_rx.take().unwrap();
26394        async move {
26395            done_edit_rx.await.unwrap();
26396            Ok(None)
26397        }
26398    });
26399
26400    start_edit_rx.await.unwrap();
26401    buffer2
26402        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
26403        .unwrap();
26404
26405    done_edit_tx.send(()).unwrap();
26406
26407    save.await.unwrap();
26408    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
26409}
26410
26411#[track_caller]
26412fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
26413    editor
26414        .all_inlays(cx)
26415        .into_iter()
26416        .filter_map(|inlay| inlay.get_color())
26417        .map(Rgba::from)
26418        .collect()
26419}