editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    edit_prediction_tests::FakeEditPredictionProvider,
    6    linked_editing_ranges::LinkedEditingRanges,
    7    scroll::scroll_amount::ScrollAmount,
    8    test::{
    9        assert_text_with_selections, build_editor,
   10        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   11        editor_test_context::EditorTestContext,
   12        select_ranges,
   13    },
   14};
   15use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   16use collections::HashMap;
   17use futures::StreamExt;
   18use gpui::{
   19    BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
   20    VisualTestContext, WindowBounds, WindowOptions, div,
   21};
   22use indoc::indoc;
   23use language::{
   24    BracketPairConfig,
   25    Capability::ReadWrite,
   26    DiagnosticSourceKind, FakeLspAdapter, IndentGuideSettings, LanguageConfig,
   27    LanguageConfigOverride, LanguageMatcher, LanguageName, Override, Point,
   28    language_settings::{
   29        CompletionSettingsContent, FormatterList, LanguageSettingsContent, LspInsertMode,
   30        SelectedFormatter,
   31    },
   32    tree_sitter_python,
   33};
   34use language_settings::Formatter;
   35use lsp::CompletionParams;
   36use multi_buffer::{IndentGuide, PathKey};
   37use parking_lot::Mutex;
   38use pretty_assertions::{assert_eq, assert_ne};
   39use project::{
   40    FakeFs,
   41    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   42    project_settings::LspSettings,
   43};
   44use serde_json::{self, json};
   45use settings::{
   46    AllLanguageSettingsContent, IndentGuideBackgroundColoring, IndentGuideColoring,
   47    ProjectSettingsContent,
   48};
   49use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   50use std::{
   51    iter,
   52    sync::atomic::{self, AtomicUsize},
   53};
   54use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
   55use text::ToPoint as _;
   56use unindent::Unindent;
   57use util::{
   58    assert_set_eq, path,
   59    rel_path::rel_path,
   60    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   61    uri,
   62};
   63use workspace::{
   64    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   65    OpenOptions, ViewId,
   66    invalid_buffer_view::InvalidBufferView,
   67    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   68    register_project_item,
   69};
   70
   71#[gpui::test]
   72fn test_edit_events(cx: &mut TestAppContext) {
   73    init_test(cx, |_| {});
   74
   75    let buffer = cx.new(|cx| {
   76        let mut buffer = language::Buffer::local("123456", cx);
   77        buffer.set_group_interval(Duration::from_secs(1));
   78        buffer
   79    });
   80
   81    let events = Rc::new(RefCell::new(Vec::new()));
   82    let editor1 = cx.add_window({
   83        let events = events.clone();
   84        |window, cx| {
   85            let entity = cx.entity();
   86            cx.subscribe_in(
   87                &entity,
   88                window,
   89                move |_, _, event: &EditorEvent, _, _| match event {
   90                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   91                    EditorEvent::BufferEdited => {
   92                        events.borrow_mut().push(("editor1", "buffer edited"))
   93                    }
   94                    _ => {}
   95                },
   96            )
   97            .detach();
   98            Editor::for_buffer(buffer.clone(), None, window, cx)
   99        }
  100    });
  101
  102    let editor2 = cx.add_window({
  103        let events = events.clone();
  104        |window, cx| {
  105            cx.subscribe_in(
  106                &cx.entity(),
  107                window,
  108                move |_, _, event: &EditorEvent, _, _| match event {
  109                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  110                    EditorEvent::BufferEdited => {
  111                        events.borrow_mut().push(("editor2", "buffer edited"))
  112                    }
  113                    _ => {}
  114                },
  115            )
  116            .detach();
  117            Editor::for_buffer(buffer.clone(), None, window, cx)
  118        }
  119    });
  120
  121    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  122
  123    // Mutating editor 1 will emit an `Edited` event only for that editor.
  124    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  125    assert_eq!(
  126        mem::take(&mut *events.borrow_mut()),
  127        [
  128            ("editor1", "edited"),
  129            ("editor1", "buffer edited"),
  130            ("editor2", "buffer edited"),
  131        ]
  132    );
  133
  134    // Mutating editor 2 will emit an `Edited` event only for that editor.
  135    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  136    assert_eq!(
  137        mem::take(&mut *events.borrow_mut()),
  138        [
  139            ("editor2", "edited"),
  140            ("editor1", "buffer edited"),
  141            ("editor2", "buffer edited"),
  142        ]
  143    );
  144
  145    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  146    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  147    assert_eq!(
  148        mem::take(&mut *events.borrow_mut()),
  149        [
  150            ("editor1", "edited"),
  151            ("editor1", "buffer edited"),
  152            ("editor2", "buffer edited"),
  153        ]
  154    );
  155
  156    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  157    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  158    assert_eq!(
  159        mem::take(&mut *events.borrow_mut()),
  160        [
  161            ("editor1", "edited"),
  162            ("editor1", "buffer edited"),
  163            ("editor2", "buffer edited"),
  164        ]
  165    );
  166
  167    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  168    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  169    assert_eq!(
  170        mem::take(&mut *events.borrow_mut()),
  171        [
  172            ("editor2", "edited"),
  173            ("editor1", "buffer edited"),
  174            ("editor2", "buffer edited"),
  175        ]
  176    );
  177
  178    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  179    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  180    assert_eq!(
  181        mem::take(&mut *events.borrow_mut()),
  182        [
  183            ("editor2", "edited"),
  184            ("editor1", "buffer edited"),
  185            ("editor2", "buffer edited"),
  186        ]
  187    );
  188
  189    // No event is emitted when the mutation is a no-op.
  190    _ = editor2.update(cx, |editor, window, cx| {
  191        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  192            s.select_ranges([0..0])
  193        });
  194
  195        editor.backspace(&Backspace, window, cx);
  196    });
  197    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  198}
  199
  200#[gpui::test]
  201fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  202    init_test(cx, |_| {});
  203
  204    let mut now = Instant::now();
  205    let group_interval = Duration::from_millis(1);
  206    let buffer = cx.new(|cx| {
  207        let mut buf = language::Buffer::local("123456", cx);
  208        buf.set_group_interval(group_interval);
  209        buf
  210    });
  211    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  212    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  213
  214    _ = editor.update(cx, |editor, window, cx| {
  215        editor.start_transaction_at(now, window, cx);
  216        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  217            s.select_ranges([2..4])
  218        });
  219
  220        editor.insert("cd", window, cx);
  221        editor.end_transaction_at(now, cx);
  222        assert_eq!(editor.text(cx), "12cd56");
  223        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  224
  225        editor.start_transaction_at(now, window, cx);
  226        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  227            s.select_ranges([4..5])
  228        });
  229        editor.insert("e", window, cx);
  230        editor.end_transaction_at(now, cx);
  231        assert_eq!(editor.text(cx), "12cde6");
  232        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  233
  234        now += group_interval + Duration::from_millis(1);
  235        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  236            s.select_ranges([2..2])
  237        });
  238
  239        // Simulate an edit in another editor
  240        buffer.update(cx, |buffer, cx| {
  241            buffer.start_transaction_at(now, cx);
  242            buffer.edit([(0..1, "a")], None, cx);
  243            buffer.edit([(1..1, "b")], None, cx);
  244            buffer.end_transaction_at(now, cx);
  245        });
  246
  247        assert_eq!(editor.text(cx), "ab2cde6");
  248        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  249
  250        // Last transaction happened past the group interval in a different editor.
  251        // Undo it individually and don't restore selections.
  252        editor.undo(&Undo, window, cx);
  253        assert_eq!(editor.text(cx), "12cde6");
  254        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  255
  256        // First two transactions happened within the group interval in this editor.
  257        // Undo them together and restore selections.
  258        editor.undo(&Undo, window, cx);
  259        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  260        assert_eq!(editor.text(cx), "123456");
  261        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  262
  263        // Redo the first two transactions together.
  264        editor.redo(&Redo, window, cx);
  265        assert_eq!(editor.text(cx), "12cde6");
  266        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  267
  268        // Redo the last transaction on its own.
  269        editor.redo(&Redo, window, cx);
  270        assert_eq!(editor.text(cx), "ab2cde6");
  271        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  272
  273        // Test empty transactions.
  274        editor.start_transaction_at(now, window, cx);
  275        editor.end_transaction_at(now, cx);
  276        editor.undo(&Undo, window, cx);
  277        assert_eq!(editor.text(cx), "12cde6");
  278    });
  279}
  280
  281#[gpui::test]
  282fn test_ime_composition(cx: &mut TestAppContext) {
  283    init_test(cx, |_| {});
  284
  285    let buffer = cx.new(|cx| {
  286        let mut buffer = language::Buffer::local("abcde", cx);
  287        // Ensure automatic grouping doesn't occur.
  288        buffer.set_group_interval(Duration::ZERO);
  289        buffer
  290    });
  291
  292    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  293    cx.add_window(|window, cx| {
  294        let mut editor = build_editor(buffer.clone(), window, cx);
  295
  296        // Start a new IME composition.
  297        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  298        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  299        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  300        assert_eq!(editor.text(cx), "äbcde");
  301        assert_eq!(
  302            editor.marked_text_ranges(cx),
  303            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  304        );
  305
  306        // Finalize IME composition.
  307        editor.replace_text_in_range(None, "ā", window, cx);
  308        assert_eq!(editor.text(cx), "ābcde");
  309        assert_eq!(editor.marked_text_ranges(cx), None);
  310
  311        // IME composition edits are grouped and are undone/redone at once.
  312        editor.undo(&Default::default(), window, cx);
  313        assert_eq!(editor.text(cx), "abcde");
  314        assert_eq!(editor.marked_text_ranges(cx), None);
  315        editor.redo(&Default::default(), window, cx);
  316        assert_eq!(editor.text(cx), "ābcde");
  317        assert_eq!(editor.marked_text_ranges(cx), None);
  318
  319        // Start a new IME composition.
  320        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  321        assert_eq!(
  322            editor.marked_text_ranges(cx),
  323            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  324        );
  325
  326        // Undoing during an IME composition cancels it.
  327        editor.undo(&Default::default(), window, cx);
  328        assert_eq!(editor.text(cx), "ābcde");
  329        assert_eq!(editor.marked_text_ranges(cx), None);
  330
  331        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  332        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  333        assert_eq!(editor.text(cx), "ābcdè");
  334        assert_eq!(
  335            editor.marked_text_ranges(cx),
  336            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  337        );
  338
  339        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  340        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  341        assert_eq!(editor.text(cx), "ābcdę");
  342        assert_eq!(editor.marked_text_ranges(cx), None);
  343
  344        // Start a new IME composition with multiple cursors.
  345        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  346            s.select_ranges([
  347                OffsetUtf16(1)..OffsetUtf16(1),
  348                OffsetUtf16(3)..OffsetUtf16(3),
  349                OffsetUtf16(5)..OffsetUtf16(5),
  350            ])
  351        });
  352        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  353        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  354        assert_eq!(
  355            editor.marked_text_ranges(cx),
  356            Some(vec![
  357                OffsetUtf16(0)..OffsetUtf16(3),
  358                OffsetUtf16(4)..OffsetUtf16(7),
  359                OffsetUtf16(8)..OffsetUtf16(11)
  360            ])
  361        );
  362
  363        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  364        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  365        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  366        assert_eq!(
  367            editor.marked_text_ranges(cx),
  368            Some(vec![
  369                OffsetUtf16(1)..OffsetUtf16(2),
  370                OffsetUtf16(5)..OffsetUtf16(6),
  371                OffsetUtf16(9)..OffsetUtf16(10)
  372            ])
  373        );
  374
  375        // Finalize IME composition with multiple cursors.
  376        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  377        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  378        assert_eq!(editor.marked_text_ranges(cx), None);
  379
  380        editor
  381    });
  382}
  383
  384#[gpui::test]
  385fn test_selection_with_mouse(cx: &mut TestAppContext) {
  386    init_test(cx, |_| {});
  387
  388    let editor = cx.add_window(|window, cx| {
  389        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  390        build_editor(buffer, window, cx)
  391    });
  392
  393    _ = editor.update(cx, |editor, window, cx| {
  394        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  395    });
  396    assert_eq!(
  397        editor
  398            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  399            .unwrap(),
  400        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  401    );
  402
  403    _ = editor.update(cx, |editor, window, cx| {
  404        editor.update_selection(
  405            DisplayPoint::new(DisplayRow(3), 3),
  406            0,
  407            gpui::Point::<f32>::default(),
  408            window,
  409            cx,
  410        );
  411    });
  412
  413    assert_eq!(
  414        editor
  415            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  416            .unwrap(),
  417        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  418    );
  419
  420    _ = editor.update(cx, |editor, window, cx| {
  421        editor.update_selection(
  422            DisplayPoint::new(DisplayRow(1), 1),
  423            0,
  424            gpui::Point::<f32>::default(),
  425            window,
  426            cx,
  427        );
  428    });
  429
  430    assert_eq!(
  431        editor
  432            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  433            .unwrap(),
  434        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  435    );
  436
  437    _ = editor.update(cx, |editor, window, cx| {
  438        editor.end_selection(window, cx);
  439        editor.update_selection(
  440            DisplayPoint::new(DisplayRow(3), 3),
  441            0,
  442            gpui::Point::<f32>::default(),
  443            window,
  444            cx,
  445        );
  446    });
  447
  448    assert_eq!(
  449        editor
  450            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  451            .unwrap(),
  452        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  453    );
  454
  455    _ = editor.update(cx, |editor, window, cx| {
  456        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  457        editor.update_selection(
  458            DisplayPoint::new(DisplayRow(0), 0),
  459            0,
  460            gpui::Point::<f32>::default(),
  461            window,
  462            cx,
  463        );
  464    });
  465
  466    assert_eq!(
  467        editor
  468            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  469            .unwrap(),
  470        [
  471            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  472            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  473        ]
  474    );
  475
  476    _ = editor.update(cx, |editor, window, cx| {
  477        editor.end_selection(window, cx);
  478    });
  479
  480    assert_eq!(
  481        editor
  482            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  483            .unwrap(),
  484        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  485    );
  486}
  487
  488#[gpui::test]
  489fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  490    init_test(cx, |_| {});
  491
  492    let editor = cx.add_window(|window, cx| {
  493        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  494        build_editor(buffer, window, cx)
  495    });
  496
  497    _ = editor.update(cx, |editor, window, cx| {
  498        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  499    });
  500
  501    _ = editor.update(cx, |editor, window, cx| {
  502        editor.end_selection(window, cx);
  503    });
  504
  505    _ = editor.update(cx, |editor, window, cx| {
  506        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  507    });
  508
  509    _ = editor.update(cx, |editor, window, cx| {
  510        editor.end_selection(window, cx);
  511    });
  512
  513    assert_eq!(
  514        editor
  515            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  516            .unwrap(),
  517        [
  518            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  519            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  520        ]
  521    );
  522
  523    _ = editor.update(cx, |editor, window, cx| {
  524        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  525    });
  526
  527    _ = editor.update(cx, |editor, window, cx| {
  528        editor.end_selection(window, cx);
  529    });
  530
  531    assert_eq!(
  532        editor
  533            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  534            .unwrap(),
  535        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  536    );
  537}
  538
  539#[gpui::test]
  540fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  541    init_test(cx, |_| {});
  542
  543    let editor = cx.add_window(|window, cx| {
  544        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  545        build_editor(buffer, window, cx)
  546    });
  547
  548    _ = editor.update(cx, |editor, window, cx| {
  549        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  550        assert_eq!(
  551            editor.selections.display_ranges(cx),
  552            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  553        );
  554    });
  555
  556    _ = editor.update(cx, |editor, window, cx| {
  557        editor.update_selection(
  558            DisplayPoint::new(DisplayRow(3), 3),
  559            0,
  560            gpui::Point::<f32>::default(),
  561            window,
  562            cx,
  563        );
  564        assert_eq!(
  565            editor.selections.display_ranges(cx),
  566            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  567        );
  568    });
  569
  570    _ = editor.update(cx, |editor, window, cx| {
  571        editor.cancel(&Cancel, window, cx);
  572        editor.update_selection(
  573            DisplayPoint::new(DisplayRow(1), 1),
  574            0,
  575            gpui::Point::<f32>::default(),
  576            window,
  577            cx,
  578        );
  579        assert_eq!(
  580            editor.selections.display_ranges(cx),
  581            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  582        );
  583    });
  584}
  585
  586#[gpui::test]
  587fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  588    init_test(cx, |_| {});
  589
  590    let editor = cx.add_window(|window, cx| {
  591        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  592        build_editor(buffer, window, cx)
  593    });
  594
  595    _ = editor.update(cx, |editor, window, cx| {
  596        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  597        assert_eq!(
  598            editor.selections.display_ranges(cx),
  599            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  600        );
  601
  602        editor.move_down(&Default::default(), window, cx);
  603        assert_eq!(
  604            editor.selections.display_ranges(cx),
  605            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  606        );
  607
  608        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  609        assert_eq!(
  610            editor.selections.display_ranges(cx),
  611            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  612        );
  613
  614        editor.move_up(&Default::default(), window, cx);
  615        assert_eq!(
  616            editor.selections.display_ranges(cx),
  617            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  618        );
  619    });
  620}
  621
  622#[gpui::test]
  623fn test_clone(cx: &mut TestAppContext) {
  624    init_test(cx, |_| {});
  625
  626    let (text, selection_ranges) = marked_text_ranges(
  627        indoc! {"
  628            one
  629            two
  630            threeˇ
  631            four
  632            fiveˇ
  633        "},
  634        true,
  635    );
  636
  637    let editor = cx.add_window(|window, cx| {
  638        let buffer = MultiBuffer::build_simple(&text, cx);
  639        build_editor(buffer, window, cx)
  640    });
  641
  642    _ = editor.update(cx, |editor, window, cx| {
  643        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  644            s.select_ranges(selection_ranges.clone())
  645        });
  646        editor.fold_creases(
  647            vec![
  648                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  649                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  650            ],
  651            true,
  652            window,
  653            cx,
  654        );
  655    });
  656
  657    let cloned_editor = editor
  658        .update(cx, |editor, _, cx| {
  659            cx.open_window(Default::default(), |window, cx| {
  660                cx.new(|cx| editor.clone(window, cx))
  661            })
  662        })
  663        .unwrap()
  664        .unwrap();
  665
  666    let snapshot = editor
  667        .update(cx, |e, window, cx| e.snapshot(window, cx))
  668        .unwrap();
  669    let cloned_snapshot = cloned_editor
  670        .update(cx, |e, window, cx| e.snapshot(window, cx))
  671        .unwrap();
  672
  673    assert_eq!(
  674        cloned_editor
  675            .update(cx, |e, _, cx| e.display_text(cx))
  676            .unwrap(),
  677        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  678    );
  679    assert_eq!(
  680        cloned_snapshot
  681            .folds_in_range(0..text.len())
  682            .collect::<Vec<_>>(),
  683        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  684    );
  685    assert_set_eq!(
  686        cloned_editor
  687            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  688            .unwrap(),
  689        editor
  690            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  691            .unwrap()
  692    );
  693    assert_set_eq!(
  694        cloned_editor
  695            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  696            .unwrap(),
  697        editor
  698            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  699            .unwrap()
  700    );
  701}
  702
  703#[gpui::test]
  704async fn test_navigation_history(cx: &mut TestAppContext) {
  705    init_test(cx, |_| {});
  706
  707    use workspace::item::Item;
  708
  709    let fs = FakeFs::new(cx.executor());
  710    let project = Project::test(fs, [], cx).await;
  711    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  712    let pane = workspace
  713        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  714        .unwrap();
  715
  716    _ = workspace.update(cx, |_v, window, cx| {
  717        cx.new(|cx| {
  718            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  719            let mut editor = build_editor(buffer, window, cx);
  720            let handle = cx.entity();
  721            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  722
  723            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  724                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  725            }
  726
  727            // Move the cursor a small distance.
  728            // Nothing is added to the navigation history.
  729            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  730                s.select_display_ranges([
  731                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  732                ])
  733            });
  734            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  735                s.select_display_ranges([
  736                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  737                ])
  738            });
  739            assert!(pop_history(&mut editor, cx).is_none());
  740
  741            // Move the cursor a large distance.
  742            // The history can jump back to the previous position.
  743            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  744                s.select_display_ranges([
  745                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  746                ])
  747            });
  748            let nav_entry = pop_history(&mut editor, cx).unwrap();
  749            editor.navigate(nav_entry.data.unwrap(), window, cx);
  750            assert_eq!(nav_entry.item.id(), cx.entity_id());
  751            assert_eq!(
  752                editor.selections.display_ranges(cx),
  753                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  754            );
  755            assert!(pop_history(&mut editor, cx).is_none());
  756
  757            // Move the cursor a small distance via the mouse.
  758            // Nothing is added to the navigation history.
  759            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  760            editor.end_selection(window, cx);
  761            assert_eq!(
  762                editor.selections.display_ranges(cx),
  763                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  764            );
  765            assert!(pop_history(&mut editor, cx).is_none());
  766
  767            // Move the cursor a large distance via the mouse.
  768            // The history can jump back to the previous position.
  769            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  770            editor.end_selection(window, cx);
  771            assert_eq!(
  772                editor.selections.display_ranges(cx),
  773                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  774            );
  775            let nav_entry = pop_history(&mut editor, cx).unwrap();
  776            editor.navigate(nav_entry.data.unwrap(), window, cx);
  777            assert_eq!(nav_entry.item.id(), cx.entity_id());
  778            assert_eq!(
  779                editor.selections.display_ranges(cx),
  780                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  781            );
  782            assert!(pop_history(&mut editor, cx).is_none());
  783
  784            // Set scroll position to check later
  785            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
  786            let original_scroll_position = editor.scroll_manager.anchor();
  787
  788            // Jump to the end of the document and adjust scroll
  789            editor.move_to_end(&MoveToEnd, window, cx);
  790            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
  791            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  792
  793            let nav_entry = pop_history(&mut editor, cx).unwrap();
  794            editor.navigate(nav_entry.data.unwrap(), window, cx);
  795            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  796
  797            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  798            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  799            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  800            let invalid_point = Point::new(9999, 0);
  801            editor.navigate(
  802                Box::new(NavigationData {
  803                    cursor_anchor: invalid_anchor,
  804                    cursor_position: invalid_point,
  805                    scroll_anchor: ScrollAnchor {
  806                        anchor: invalid_anchor,
  807                        offset: Default::default(),
  808                    },
  809                    scroll_top_row: invalid_point.row,
  810                }),
  811                window,
  812                cx,
  813            );
  814            assert_eq!(
  815                editor.selections.display_ranges(cx),
  816                &[editor.max_point(cx)..editor.max_point(cx)]
  817            );
  818            assert_eq!(
  819                editor.scroll_position(cx),
  820                gpui::Point::new(0., editor.max_point(cx).row().as_f32())
  821            );
  822
  823            editor
  824        })
  825    });
  826}
  827
  828#[gpui::test]
  829fn test_cancel(cx: &mut TestAppContext) {
  830    init_test(cx, |_| {});
  831
  832    let editor = cx.add_window(|window, cx| {
  833        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  834        build_editor(buffer, window, cx)
  835    });
  836
  837    _ = editor.update(cx, |editor, window, cx| {
  838        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  839        editor.update_selection(
  840            DisplayPoint::new(DisplayRow(1), 1),
  841            0,
  842            gpui::Point::<f32>::default(),
  843            window,
  844            cx,
  845        );
  846        editor.end_selection(window, cx);
  847
  848        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  849        editor.update_selection(
  850            DisplayPoint::new(DisplayRow(0), 3),
  851            0,
  852            gpui::Point::<f32>::default(),
  853            window,
  854            cx,
  855        );
  856        editor.end_selection(window, cx);
  857        assert_eq!(
  858            editor.selections.display_ranges(cx),
  859            [
  860                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  861                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  862            ]
  863        );
  864    });
  865
  866    _ = editor.update(cx, |editor, window, cx| {
  867        editor.cancel(&Cancel, window, cx);
  868        assert_eq!(
  869            editor.selections.display_ranges(cx),
  870            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  871        );
  872    });
  873
  874    _ = editor.update(cx, |editor, window, cx| {
  875        editor.cancel(&Cancel, window, cx);
  876        assert_eq!(
  877            editor.selections.display_ranges(cx),
  878            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  879        );
  880    });
  881}
  882
  883#[gpui::test]
  884fn test_fold_action(cx: &mut TestAppContext) {
  885    init_test(cx, |_| {});
  886
  887    let editor = cx.add_window(|window, cx| {
  888        let buffer = MultiBuffer::build_simple(
  889            &"
  890                impl Foo {
  891                    // Hello!
  892
  893                    fn a() {
  894                        1
  895                    }
  896
  897                    fn b() {
  898                        2
  899                    }
  900
  901                    fn c() {
  902                        3
  903                    }
  904                }
  905            "
  906            .unindent(),
  907            cx,
  908        );
  909        build_editor(buffer, window, cx)
  910    });
  911
  912    _ = editor.update(cx, |editor, window, cx| {
  913        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  914            s.select_display_ranges([
  915                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  916            ]);
  917        });
  918        editor.fold(&Fold, window, cx);
  919        assert_eq!(
  920            editor.display_text(cx),
  921            "
  922                impl Foo {
  923                    // Hello!
  924
  925                    fn a() {
  926                        1
  927                    }
  928
  929                    fn b() {⋯
  930                    }
  931
  932                    fn c() {⋯
  933                    }
  934                }
  935            "
  936            .unindent(),
  937        );
  938
  939        editor.fold(&Fold, window, cx);
  940        assert_eq!(
  941            editor.display_text(cx),
  942            "
  943                impl Foo {⋯
  944                }
  945            "
  946            .unindent(),
  947        );
  948
  949        editor.unfold_lines(&UnfoldLines, window, cx);
  950        assert_eq!(
  951            editor.display_text(cx),
  952            "
  953                impl Foo {
  954                    // Hello!
  955
  956                    fn a() {
  957                        1
  958                    }
  959
  960                    fn b() {⋯
  961                    }
  962
  963                    fn c() {⋯
  964                    }
  965                }
  966            "
  967            .unindent(),
  968        );
  969
  970        editor.unfold_lines(&UnfoldLines, window, cx);
  971        assert_eq!(
  972            editor.display_text(cx),
  973            editor.buffer.read(cx).read(cx).text()
  974        );
  975    });
  976}
  977
  978#[gpui::test]
  979fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  980    init_test(cx, |_| {});
  981
  982    let editor = cx.add_window(|window, cx| {
  983        let buffer = MultiBuffer::build_simple(
  984            &"
  985                class Foo:
  986                    # Hello!
  987
  988                    def a():
  989                        print(1)
  990
  991                    def b():
  992                        print(2)
  993
  994                    def c():
  995                        print(3)
  996            "
  997            .unindent(),
  998            cx,
  999        );
 1000        build_editor(buffer, window, cx)
 1001    });
 1002
 1003    _ = editor.update(cx, |editor, window, cx| {
 1004        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1005            s.select_display_ranges([
 1006                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1007            ]);
 1008        });
 1009        editor.fold(&Fold, window, cx);
 1010        assert_eq!(
 1011            editor.display_text(cx),
 1012            "
 1013                class Foo:
 1014                    # Hello!
 1015
 1016                    def a():
 1017                        print(1)
 1018
 1019                    def b():⋯
 1020
 1021                    def c():⋯
 1022            "
 1023            .unindent(),
 1024        );
 1025
 1026        editor.fold(&Fold, window, cx);
 1027        assert_eq!(
 1028            editor.display_text(cx),
 1029            "
 1030                class Foo:⋯
 1031            "
 1032            .unindent(),
 1033        );
 1034
 1035        editor.unfold_lines(&UnfoldLines, window, cx);
 1036        assert_eq!(
 1037            editor.display_text(cx),
 1038            "
 1039                class Foo:
 1040                    # Hello!
 1041
 1042                    def a():
 1043                        print(1)
 1044
 1045                    def b():⋯
 1046
 1047                    def c():⋯
 1048            "
 1049            .unindent(),
 1050        );
 1051
 1052        editor.unfold_lines(&UnfoldLines, window, cx);
 1053        assert_eq!(
 1054            editor.display_text(cx),
 1055            editor.buffer.read(cx).read(cx).text()
 1056        );
 1057    });
 1058}
 1059
 1060#[gpui::test]
 1061fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1062    init_test(cx, |_| {});
 1063
 1064    let editor = cx.add_window(|window, cx| {
 1065        let buffer = MultiBuffer::build_simple(
 1066            &"
 1067                class Foo:
 1068                    # Hello!
 1069
 1070                    def a():
 1071                        print(1)
 1072
 1073                    def b():
 1074                        print(2)
 1075
 1076
 1077                    def c():
 1078                        print(3)
 1079
 1080
 1081            "
 1082            .unindent(),
 1083            cx,
 1084        );
 1085        build_editor(buffer, window, cx)
 1086    });
 1087
 1088    _ = editor.update(cx, |editor, window, cx| {
 1089        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1090            s.select_display_ranges([
 1091                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1092            ]);
 1093        });
 1094        editor.fold(&Fold, window, cx);
 1095        assert_eq!(
 1096            editor.display_text(cx),
 1097            "
 1098                class Foo:
 1099                    # Hello!
 1100
 1101                    def a():
 1102                        print(1)
 1103
 1104                    def b():⋯
 1105
 1106
 1107                    def c():⋯
 1108
 1109
 1110            "
 1111            .unindent(),
 1112        );
 1113
 1114        editor.fold(&Fold, window, cx);
 1115        assert_eq!(
 1116            editor.display_text(cx),
 1117            "
 1118                class Foo:⋯
 1119
 1120
 1121            "
 1122            .unindent(),
 1123        );
 1124
 1125        editor.unfold_lines(&UnfoldLines, window, cx);
 1126        assert_eq!(
 1127            editor.display_text(cx),
 1128            "
 1129                class Foo:
 1130                    # Hello!
 1131
 1132                    def a():
 1133                        print(1)
 1134
 1135                    def b():⋯
 1136
 1137
 1138                    def c():⋯
 1139
 1140
 1141            "
 1142            .unindent(),
 1143        );
 1144
 1145        editor.unfold_lines(&UnfoldLines, window, cx);
 1146        assert_eq!(
 1147            editor.display_text(cx),
 1148            editor.buffer.read(cx).read(cx).text()
 1149        );
 1150    });
 1151}
 1152
 1153#[gpui::test]
 1154fn test_fold_at_level(cx: &mut TestAppContext) {
 1155    init_test(cx, |_| {});
 1156
 1157    let editor = cx.add_window(|window, cx| {
 1158        let buffer = MultiBuffer::build_simple(
 1159            &"
 1160                class Foo:
 1161                    # Hello!
 1162
 1163                    def a():
 1164                        print(1)
 1165
 1166                    def b():
 1167                        print(2)
 1168
 1169
 1170                class Bar:
 1171                    # World!
 1172
 1173                    def a():
 1174                        print(1)
 1175
 1176                    def b():
 1177                        print(2)
 1178
 1179
 1180            "
 1181            .unindent(),
 1182            cx,
 1183        );
 1184        build_editor(buffer, window, cx)
 1185    });
 1186
 1187    _ = editor.update(cx, |editor, window, cx| {
 1188        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1189        assert_eq!(
 1190            editor.display_text(cx),
 1191            "
 1192                class Foo:
 1193                    # Hello!
 1194
 1195                    def a():⋯
 1196
 1197                    def b():⋯
 1198
 1199
 1200                class Bar:
 1201                    # World!
 1202
 1203                    def a():⋯
 1204
 1205                    def b():⋯
 1206
 1207
 1208            "
 1209            .unindent(),
 1210        );
 1211
 1212        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1213        assert_eq!(
 1214            editor.display_text(cx),
 1215            "
 1216                class Foo:⋯
 1217
 1218
 1219                class Bar:⋯
 1220
 1221
 1222            "
 1223            .unindent(),
 1224        );
 1225
 1226        editor.unfold_all(&UnfoldAll, window, cx);
 1227        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1228        assert_eq!(
 1229            editor.display_text(cx),
 1230            "
 1231                class Foo:
 1232                    # Hello!
 1233
 1234                    def a():
 1235                        print(1)
 1236
 1237                    def b():
 1238                        print(2)
 1239
 1240
 1241                class Bar:
 1242                    # World!
 1243
 1244                    def a():
 1245                        print(1)
 1246
 1247                    def b():
 1248                        print(2)
 1249
 1250
 1251            "
 1252            .unindent(),
 1253        );
 1254
 1255        assert_eq!(
 1256            editor.display_text(cx),
 1257            editor.buffer.read(cx).read(cx).text()
 1258        );
 1259    });
 1260}
 1261
 1262#[gpui::test]
 1263fn test_move_cursor(cx: &mut TestAppContext) {
 1264    init_test(cx, |_| {});
 1265
 1266    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1267    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1268
 1269    buffer.update(cx, |buffer, cx| {
 1270        buffer.edit(
 1271            vec![
 1272                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1273                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1274            ],
 1275            None,
 1276            cx,
 1277        );
 1278    });
 1279    _ = editor.update(cx, |editor, window, cx| {
 1280        assert_eq!(
 1281            editor.selections.display_ranges(cx),
 1282            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1283        );
 1284
 1285        editor.move_down(&MoveDown, window, cx);
 1286        assert_eq!(
 1287            editor.selections.display_ranges(cx),
 1288            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1289        );
 1290
 1291        editor.move_right(&MoveRight, window, cx);
 1292        assert_eq!(
 1293            editor.selections.display_ranges(cx),
 1294            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1295        );
 1296
 1297        editor.move_left(&MoveLeft, window, cx);
 1298        assert_eq!(
 1299            editor.selections.display_ranges(cx),
 1300            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1301        );
 1302
 1303        editor.move_up(&MoveUp, window, cx);
 1304        assert_eq!(
 1305            editor.selections.display_ranges(cx),
 1306            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1307        );
 1308
 1309        editor.move_to_end(&MoveToEnd, window, cx);
 1310        assert_eq!(
 1311            editor.selections.display_ranges(cx),
 1312            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1313        );
 1314
 1315        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1316        assert_eq!(
 1317            editor.selections.display_ranges(cx),
 1318            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1319        );
 1320
 1321        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1322            s.select_display_ranges([
 1323                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1324            ]);
 1325        });
 1326        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1327        assert_eq!(
 1328            editor.selections.display_ranges(cx),
 1329            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1330        );
 1331
 1332        editor.select_to_end(&SelectToEnd, window, cx);
 1333        assert_eq!(
 1334            editor.selections.display_ranges(cx),
 1335            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1336        );
 1337    });
 1338}
 1339
 1340#[gpui::test]
 1341fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1342    init_test(cx, |_| {});
 1343
 1344    let editor = cx.add_window(|window, cx| {
 1345        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1346        build_editor(buffer, window, cx)
 1347    });
 1348
 1349    assert_eq!('🟥'.len_utf8(), 4);
 1350    assert_eq!('α'.len_utf8(), 2);
 1351
 1352    _ = editor.update(cx, |editor, window, cx| {
 1353        editor.fold_creases(
 1354            vec![
 1355                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1356                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1357                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1358            ],
 1359            true,
 1360            window,
 1361            cx,
 1362        );
 1363        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1364
 1365        editor.move_right(&MoveRight, window, cx);
 1366        assert_eq!(
 1367            editor.selections.display_ranges(cx),
 1368            &[empty_range(0, "🟥".len())]
 1369        );
 1370        editor.move_right(&MoveRight, window, cx);
 1371        assert_eq!(
 1372            editor.selections.display_ranges(cx),
 1373            &[empty_range(0, "🟥🟧".len())]
 1374        );
 1375        editor.move_right(&MoveRight, window, cx);
 1376        assert_eq!(
 1377            editor.selections.display_ranges(cx),
 1378            &[empty_range(0, "🟥🟧⋯".len())]
 1379        );
 1380
 1381        editor.move_down(&MoveDown, window, cx);
 1382        assert_eq!(
 1383            editor.selections.display_ranges(cx),
 1384            &[empty_range(1, "ab⋯e".len())]
 1385        );
 1386        editor.move_left(&MoveLeft, window, cx);
 1387        assert_eq!(
 1388            editor.selections.display_ranges(cx),
 1389            &[empty_range(1, "ab⋯".len())]
 1390        );
 1391        editor.move_left(&MoveLeft, window, cx);
 1392        assert_eq!(
 1393            editor.selections.display_ranges(cx),
 1394            &[empty_range(1, "ab".len())]
 1395        );
 1396        editor.move_left(&MoveLeft, window, cx);
 1397        assert_eq!(
 1398            editor.selections.display_ranges(cx),
 1399            &[empty_range(1, "a".len())]
 1400        );
 1401
 1402        editor.move_down(&MoveDown, window, cx);
 1403        assert_eq!(
 1404            editor.selections.display_ranges(cx),
 1405            &[empty_range(2, "α".len())]
 1406        );
 1407        editor.move_right(&MoveRight, window, cx);
 1408        assert_eq!(
 1409            editor.selections.display_ranges(cx),
 1410            &[empty_range(2, "αβ".len())]
 1411        );
 1412        editor.move_right(&MoveRight, window, cx);
 1413        assert_eq!(
 1414            editor.selections.display_ranges(cx),
 1415            &[empty_range(2, "αβ⋯".len())]
 1416        );
 1417        editor.move_right(&MoveRight, window, cx);
 1418        assert_eq!(
 1419            editor.selections.display_ranges(cx),
 1420            &[empty_range(2, "αβ⋯ε".len())]
 1421        );
 1422
 1423        editor.move_up(&MoveUp, window, cx);
 1424        assert_eq!(
 1425            editor.selections.display_ranges(cx),
 1426            &[empty_range(1, "ab⋯e".len())]
 1427        );
 1428        editor.move_down(&MoveDown, window, cx);
 1429        assert_eq!(
 1430            editor.selections.display_ranges(cx),
 1431            &[empty_range(2, "αβ⋯ε".len())]
 1432        );
 1433        editor.move_up(&MoveUp, window, cx);
 1434        assert_eq!(
 1435            editor.selections.display_ranges(cx),
 1436            &[empty_range(1, "ab⋯e".len())]
 1437        );
 1438
 1439        editor.move_up(&MoveUp, window, cx);
 1440        assert_eq!(
 1441            editor.selections.display_ranges(cx),
 1442            &[empty_range(0, "🟥🟧".len())]
 1443        );
 1444        editor.move_left(&MoveLeft, window, cx);
 1445        assert_eq!(
 1446            editor.selections.display_ranges(cx),
 1447            &[empty_range(0, "🟥".len())]
 1448        );
 1449        editor.move_left(&MoveLeft, window, cx);
 1450        assert_eq!(
 1451            editor.selections.display_ranges(cx),
 1452            &[empty_range(0, "".len())]
 1453        );
 1454    });
 1455}
 1456
 1457#[gpui::test]
 1458fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1459    init_test(cx, |_| {});
 1460
 1461    let editor = cx.add_window(|window, cx| {
 1462        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1463        build_editor(buffer, window, cx)
 1464    });
 1465    _ = editor.update(cx, |editor, window, cx| {
 1466        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1467            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1468        });
 1469
 1470        // moving above start of document should move selection to start of document,
 1471        // but the next move down should still be at the original goal_x
 1472        editor.move_up(&MoveUp, window, cx);
 1473        assert_eq!(
 1474            editor.selections.display_ranges(cx),
 1475            &[empty_range(0, "".len())]
 1476        );
 1477
 1478        editor.move_down(&MoveDown, window, cx);
 1479        assert_eq!(
 1480            editor.selections.display_ranges(cx),
 1481            &[empty_range(1, "abcd".len())]
 1482        );
 1483
 1484        editor.move_down(&MoveDown, window, cx);
 1485        assert_eq!(
 1486            editor.selections.display_ranges(cx),
 1487            &[empty_range(2, "αβγ".len())]
 1488        );
 1489
 1490        editor.move_down(&MoveDown, window, cx);
 1491        assert_eq!(
 1492            editor.selections.display_ranges(cx),
 1493            &[empty_range(3, "abcd".len())]
 1494        );
 1495
 1496        editor.move_down(&MoveDown, window, cx);
 1497        assert_eq!(
 1498            editor.selections.display_ranges(cx),
 1499            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1500        );
 1501
 1502        // moving past end of document should not change goal_x
 1503        editor.move_down(&MoveDown, window, cx);
 1504        assert_eq!(
 1505            editor.selections.display_ranges(cx),
 1506            &[empty_range(5, "".len())]
 1507        );
 1508
 1509        editor.move_down(&MoveDown, window, cx);
 1510        assert_eq!(
 1511            editor.selections.display_ranges(cx),
 1512            &[empty_range(5, "".len())]
 1513        );
 1514
 1515        editor.move_up(&MoveUp, window, cx);
 1516        assert_eq!(
 1517            editor.selections.display_ranges(cx),
 1518            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1519        );
 1520
 1521        editor.move_up(&MoveUp, window, cx);
 1522        assert_eq!(
 1523            editor.selections.display_ranges(cx),
 1524            &[empty_range(3, "abcd".len())]
 1525        );
 1526
 1527        editor.move_up(&MoveUp, window, cx);
 1528        assert_eq!(
 1529            editor.selections.display_ranges(cx),
 1530            &[empty_range(2, "αβγ".len())]
 1531        );
 1532    });
 1533}
 1534
 1535#[gpui::test]
 1536fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1537    init_test(cx, |_| {});
 1538    let move_to_beg = MoveToBeginningOfLine {
 1539        stop_at_soft_wraps: true,
 1540        stop_at_indent: true,
 1541    };
 1542
 1543    let delete_to_beg = DeleteToBeginningOfLine {
 1544        stop_at_indent: false,
 1545    };
 1546
 1547    let move_to_end = MoveToEndOfLine {
 1548        stop_at_soft_wraps: true,
 1549    };
 1550
 1551    let editor = cx.add_window(|window, cx| {
 1552        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1553        build_editor(buffer, window, cx)
 1554    });
 1555    _ = editor.update(cx, |editor, window, cx| {
 1556        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1557            s.select_display_ranges([
 1558                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1559                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1560            ]);
 1561        });
 1562    });
 1563
 1564    _ = editor.update(cx, |editor, window, cx| {
 1565        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1566        assert_eq!(
 1567            editor.selections.display_ranges(cx),
 1568            &[
 1569                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1570                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1571            ]
 1572        );
 1573    });
 1574
 1575    _ = editor.update(cx, |editor, window, cx| {
 1576        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1577        assert_eq!(
 1578            editor.selections.display_ranges(cx),
 1579            &[
 1580                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1581                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1582            ]
 1583        );
 1584    });
 1585
 1586    _ = editor.update(cx, |editor, window, cx| {
 1587        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1588        assert_eq!(
 1589            editor.selections.display_ranges(cx),
 1590            &[
 1591                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1592                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1593            ]
 1594        );
 1595    });
 1596
 1597    _ = editor.update(cx, |editor, window, cx| {
 1598        editor.move_to_end_of_line(&move_to_end, window, cx);
 1599        assert_eq!(
 1600            editor.selections.display_ranges(cx),
 1601            &[
 1602                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1603                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1604            ]
 1605        );
 1606    });
 1607
 1608    // Moving to the end of line again is a no-op.
 1609    _ = editor.update(cx, |editor, window, cx| {
 1610        editor.move_to_end_of_line(&move_to_end, window, cx);
 1611        assert_eq!(
 1612            editor.selections.display_ranges(cx),
 1613            &[
 1614                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1615                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1616            ]
 1617        );
 1618    });
 1619
 1620    _ = editor.update(cx, |editor, window, cx| {
 1621        editor.move_left(&MoveLeft, window, cx);
 1622        editor.select_to_beginning_of_line(
 1623            &SelectToBeginningOfLine {
 1624                stop_at_soft_wraps: true,
 1625                stop_at_indent: true,
 1626            },
 1627            window,
 1628            cx,
 1629        );
 1630        assert_eq!(
 1631            editor.selections.display_ranges(cx),
 1632            &[
 1633                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1634                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1635            ]
 1636        );
 1637    });
 1638
 1639    _ = editor.update(cx, |editor, window, cx| {
 1640        editor.select_to_beginning_of_line(
 1641            &SelectToBeginningOfLine {
 1642                stop_at_soft_wraps: true,
 1643                stop_at_indent: true,
 1644            },
 1645            window,
 1646            cx,
 1647        );
 1648        assert_eq!(
 1649            editor.selections.display_ranges(cx),
 1650            &[
 1651                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1652                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1653            ]
 1654        );
 1655    });
 1656
 1657    _ = editor.update(cx, |editor, window, cx| {
 1658        editor.select_to_beginning_of_line(
 1659            &SelectToBeginningOfLine {
 1660                stop_at_soft_wraps: true,
 1661                stop_at_indent: true,
 1662            },
 1663            window,
 1664            cx,
 1665        );
 1666        assert_eq!(
 1667            editor.selections.display_ranges(cx),
 1668            &[
 1669                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1670                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1671            ]
 1672        );
 1673    });
 1674
 1675    _ = editor.update(cx, |editor, window, cx| {
 1676        editor.select_to_end_of_line(
 1677            &SelectToEndOfLine {
 1678                stop_at_soft_wraps: true,
 1679            },
 1680            window,
 1681            cx,
 1682        );
 1683        assert_eq!(
 1684            editor.selections.display_ranges(cx),
 1685            &[
 1686                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1687                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1688            ]
 1689        );
 1690    });
 1691
 1692    _ = editor.update(cx, |editor, window, cx| {
 1693        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1694        assert_eq!(editor.display_text(cx), "ab\n  de");
 1695        assert_eq!(
 1696            editor.selections.display_ranges(cx),
 1697            &[
 1698                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1699                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1700            ]
 1701        );
 1702    });
 1703
 1704    _ = editor.update(cx, |editor, window, cx| {
 1705        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1706        assert_eq!(editor.display_text(cx), "\n");
 1707        assert_eq!(
 1708            editor.selections.display_ranges(cx),
 1709            &[
 1710                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1711                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1712            ]
 1713        );
 1714    });
 1715}
 1716
 1717#[gpui::test]
 1718fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1719    init_test(cx, |_| {});
 1720    let move_to_beg = MoveToBeginningOfLine {
 1721        stop_at_soft_wraps: false,
 1722        stop_at_indent: false,
 1723    };
 1724
 1725    let move_to_end = MoveToEndOfLine {
 1726        stop_at_soft_wraps: false,
 1727    };
 1728
 1729    let editor = cx.add_window(|window, cx| {
 1730        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1731        build_editor(buffer, window, cx)
 1732    });
 1733
 1734    _ = editor.update(cx, |editor, window, cx| {
 1735        editor.set_wrap_width(Some(140.0.into()), cx);
 1736
 1737        // We expect the following lines after wrapping
 1738        // ```
 1739        // thequickbrownfox
 1740        // jumpedoverthelazydo
 1741        // gs
 1742        // ```
 1743        // The final `gs` was soft-wrapped onto a new line.
 1744        assert_eq!(
 1745            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1746            editor.display_text(cx),
 1747        );
 1748
 1749        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1750        // Start the cursor at the `k` on the first line
 1751        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1752            s.select_display_ranges([
 1753                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1754            ]);
 1755        });
 1756
 1757        // Moving to the beginning of the line should put us at the beginning of the line.
 1758        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1759        assert_eq!(
 1760            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1761            editor.selections.display_ranges(cx)
 1762        );
 1763
 1764        // Moving to the end of the line should put us at the end of the line.
 1765        editor.move_to_end_of_line(&move_to_end, window, cx);
 1766        assert_eq!(
 1767            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1768            editor.selections.display_ranges(cx)
 1769        );
 1770
 1771        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1772        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1773        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1774            s.select_display_ranges([
 1775                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1776            ]);
 1777        });
 1778
 1779        // Moving to the beginning of the line should put us at the start of the second line of
 1780        // display text, i.e., the `j`.
 1781        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1782        assert_eq!(
 1783            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1784            editor.selections.display_ranges(cx)
 1785        );
 1786
 1787        // Moving to the beginning of the line again should be a no-op.
 1788        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1789        assert_eq!(
 1790            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1791            editor.selections.display_ranges(cx)
 1792        );
 1793
 1794        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1795        // next display line.
 1796        editor.move_to_end_of_line(&move_to_end, window, cx);
 1797        assert_eq!(
 1798            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1799            editor.selections.display_ranges(cx)
 1800        );
 1801
 1802        // Moving to the end of the line again should be a no-op.
 1803        editor.move_to_end_of_line(&move_to_end, window, cx);
 1804        assert_eq!(
 1805            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1806            editor.selections.display_ranges(cx)
 1807        );
 1808    });
 1809}
 1810
 1811#[gpui::test]
 1812fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1813    init_test(cx, |_| {});
 1814
 1815    let move_to_beg = MoveToBeginningOfLine {
 1816        stop_at_soft_wraps: true,
 1817        stop_at_indent: true,
 1818    };
 1819
 1820    let select_to_beg = SelectToBeginningOfLine {
 1821        stop_at_soft_wraps: true,
 1822        stop_at_indent: true,
 1823    };
 1824
 1825    let delete_to_beg = DeleteToBeginningOfLine {
 1826        stop_at_indent: true,
 1827    };
 1828
 1829    let move_to_end = MoveToEndOfLine {
 1830        stop_at_soft_wraps: false,
 1831    };
 1832
 1833    let editor = cx.add_window(|window, cx| {
 1834        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1835        build_editor(buffer, window, cx)
 1836    });
 1837
 1838    _ = editor.update(cx, |editor, window, cx| {
 1839        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1840            s.select_display_ranges([
 1841                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1842                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1843            ]);
 1844        });
 1845
 1846        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1847        // and the second cursor at the first non-whitespace character in the line.
 1848        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1849        assert_eq!(
 1850            editor.selections.display_ranges(cx),
 1851            &[
 1852                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1853                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1854            ]
 1855        );
 1856
 1857        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1858        // and should move the second cursor to the beginning of the line.
 1859        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1860        assert_eq!(
 1861            editor.selections.display_ranges(cx),
 1862            &[
 1863                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1864                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1865            ]
 1866        );
 1867
 1868        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1869        // and should move the second cursor back to the first non-whitespace character in the line.
 1870        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1871        assert_eq!(
 1872            editor.selections.display_ranges(cx),
 1873            &[
 1874                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1875                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1876            ]
 1877        );
 1878
 1879        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1880        // and to the first non-whitespace character in the line for the second cursor.
 1881        editor.move_to_end_of_line(&move_to_end, window, cx);
 1882        editor.move_left(&MoveLeft, window, cx);
 1883        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1884        assert_eq!(
 1885            editor.selections.display_ranges(cx),
 1886            &[
 1887                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1888                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1889            ]
 1890        );
 1891
 1892        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1893        // and should select to the beginning of the line for the second cursor.
 1894        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1895        assert_eq!(
 1896            editor.selections.display_ranges(cx),
 1897            &[
 1898                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1899                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1900            ]
 1901        );
 1902
 1903        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1904        // and should delete to the first non-whitespace character in the line for the second cursor.
 1905        editor.move_to_end_of_line(&move_to_end, window, cx);
 1906        editor.move_left(&MoveLeft, window, cx);
 1907        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1908        assert_eq!(editor.text(cx), "c\n  f");
 1909    });
 1910}
 1911
 1912#[gpui::test]
 1913fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 1914    init_test(cx, |_| {});
 1915
 1916    let move_to_beg = MoveToBeginningOfLine {
 1917        stop_at_soft_wraps: true,
 1918        stop_at_indent: true,
 1919    };
 1920
 1921    let editor = cx.add_window(|window, cx| {
 1922        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 1923        build_editor(buffer, window, cx)
 1924    });
 1925
 1926    _ = editor.update(cx, |editor, window, cx| {
 1927        // test cursor between line_start and indent_start
 1928        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1929            s.select_display_ranges([
 1930                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 1931            ]);
 1932        });
 1933
 1934        // cursor should move to line_start
 1935        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1936        assert_eq!(
 1937            editor.selections.display_ranges(cx),
 1938            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1939        );
 1940
 1941        // cursor should move to indent_start
 1942        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1943        assert_eq!(
 1944            editor.selections.display_ranges(cx),
 1945            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 1946        );
 1947
 1948        // cursor should move to back to line_start
 1949        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1950        assert_eq!(
 1951            editor.selections.display_ranges(cx),
 1952            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1953        );
 1954    });
 1955}
 1956
 1957#[gpui::test]
 1958fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1959    init_test(cx, |_| {});
 1960
 1961    let editor = cx.add_window(|window, cx| {
 1962        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1963        build_editor(buffer, window, cx)
 1964    });
 1965    _ = editor.update(cx, |editor, window, cx| {
 1966        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1967            s.select_display_ranges([
 1968                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1969                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1970            ])
 1971        });
 1972        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1973        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1974
 1975        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1976        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1977
 1978        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1979        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1980
 1981        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1982        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1983
 1984        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1985        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 1986
 1987        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1988        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1989
 1990        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1991        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1992
 1993        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1994        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1995
 1996        editor.move_right(&MoveRight, window, cx);
 1997        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1998        assert_selection_ranges(
 1999            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 2000            editor,
 2001            cx,
 2002        );
 2003
 2004        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2005        assert_selection_ranges(
 2006            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2007            editor,
 2008            cx,
 2009        );
 2010
 2011        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2012        assert_selection_ranges(
 2013            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2014            editor,
 2015            cx,
 2016        );
 2017    });
 2018}
 2019
 2020#[gpui::test]
 2021fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2022    init_test(cx, |_| {});
 2023
 2024    let editor = cx.add_window(|window, cx| {
 2025        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2026        build_editor(buffer, window, cx)
 2027    });
 2028
 2029    _ = editor.update(cx, |editor, window, cx| {
 2030        editor.set_wrap_width(Some(140.0.into()), cx);
 2031        assert_eq!(
 2032            editor.display_text(cx),
 2033            "use one::{\n    two::three::\n    four::five\n};"
 2034        );
 2035
 2036        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2037            s.select_display_ranges([
 2038                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2039            ]);
 2040        });
 2041
 2042        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2043        assert_eq!(
 2044            editor.selections.display_ranges(cx),
 2045            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2046        );
 2047
 2048        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2049        assert_eq!(
 2050            editor.selections.display_ranges(cx),
 2051            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2052        );
 2053
 2054        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2055        assert_eq!(
 2056            editor.selections.display_ranges(cx),
 2057            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2058        );
 2059
 2060        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2061        assert_eq!(
 2062            editor.selections.display_ranges(cx),
 2063            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2064        );
 2065
 2066        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2067        assert_eq!(
 2068            editor.selections.display_ranges(cx),
 2069            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2070        );
 2071
 2072        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2073        assert_eq!(
 2074            editor.selections.display_ranges(cx),
 2075            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2076        );
 2077    });
 2078}
 2079
 2080#[gpui::test]
 2081async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2082    init_test(cx, |_| {});
 2083    let mut cx = EditorTestContext::new(cx).await;
 2084
 2085    let line_height = cx.editor(|editor, window, _| {
 2086        editor
 2087            .style()
 2088            .unwrap()
 2089            .text
 2090            .line_height_in_pixels(window.rem_size())
 2091    });
 2092    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2093
 2094    cx.set_state(
 2095        &r#"ˇone
 2096        two
 2097
 2098        three
 2099        fourˇ
 2100        five
 2101
 2102        six"#
 2103            .unindent(),
 2104    );
 2105
 2106    cx.update_editor(|editor, window, cx| {
 2107        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2108    });
 2109    cx.assert_editor_state(
 2110        &r#"one
 2111        two
 2112        ˇ
 2113        three
 2114        four
 2115        five
 2116        ˇ
 2117        six"#
 2118            .unindent(),
 2119    );
 2120
 2121    cx.update_editor(|editor, window, cx| {
 2122        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2123    });
 2124    cx.assert_editor_state(
 2125        &r#"one
 2126        two
 2127
 2128        three
 2129        four
 2130        five
 2131        ˇ
 2132        sixˇ"#
 2133            .unindent(),
 2134    );
 2135
 2136    cx.update_editor(|editor, window, cx| {
 2137        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2138    });
 2139    cx.assert_editor_state(
 2140        &r#"one
 2141        two
 2142
 2143        three
 2144        four
 2145        five
 2146
 2147        sixˇ"#
 2148            .unindent(),
 2149    );
 2150
 2151    cx.update_editor(|editor, window, cx| {
 2152        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2153    });
 2154    cx.assert_editor_state(
 2155        &r#"one
 2156        two
 2157
 2158        three
 2159        four
 2160        five
 2161        ˇ
 2162        six"#
 2163            .unindent(),
 2164    );
 2165
 2166    cx.update_editor(|editor, window, cx| {
 2167        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2168    });
 2169    cx.assert_editor_state(
 2170        &r#"one
 2171        two
 2172        ˇ
 2173        three
 2174        four
 2175        five
 2176
 2177        six"#
 2178            .unindent(),
 2179    );
 2180
 2181    cx.update_editor(|editor, window, cx| {
 2182        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2183    });
 2184    cx.assert_editor_state(
 2185        &r#"ˇone
 2186        two
 2187
 2188        three
 2189        four
 2190        five
 2191
 2192        six"#
 2193            .unindent(),
 2194    );
 2195}
 2196
 2197#[gpui::test]
 2198async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2199    init_test(cx, |_| {});
 2200    let mut cx = EditorTestContext::new(cx).await;
 2201    let line_height = cx.editor(|editor, window, _| {
 2202        editor
 2203            .style()
 2204            .unwrap()
 2205            .text
 2206            .line_height_in_pixels(window.rem_size())
 2207    });
 2208    let window = cx.window;
 2209    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2210
 2211    cx.set_state(
 2212        r#"ˇone
 2213        two
 2214        three
 2215        four
 2216        five
 2217        six
 2218        seven
 2219        eight
 2220        nine
 2221        ten
 2222        "#,
 2223    );
 2224
 2225    cx.update_editor(|editor, window, cx| {
 2226        assert_eq!(
 2227            editor.snapshot(window, cx).scroll_position(),
 2228            gpui::Point::new(0., 0.)
 2229        );
 2230        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2231        assert_eq!(
 2232            editor.snapshot(window, cx).scroll_position(),
 2233            gpui::Point::new(0., 3.)
 2234        );
 2235        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2236        assert_eq!(
 2237            editor.snapshot(window, cx).scroll_position(),
 2238            gpui::Point::new(0., 6.)
 2239        );
 2240        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2241        assert_eq!(
 2242            editor.snapshot(window, cx).scroll_position(),
 2243            gpui::Point::new(0., 3.)
 2244        );
 2245
 2246        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2247        assert_eq!(
 2248            editor.snapshot(window, cx).scroll_position(),
 2249            gpui::Point::new(0., 1.)
 2250        );
 2251        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2252        assert_eq!(
 2253            editor.snapshot(window, cx).scroll_position(),
 2254            gpui::Point::new(0., 3.)
 2255        );
 2256    });
 2257}
 2258
 2259#[gpui::test]
 2260async fn test_autoscroll(cx: &mut TestAppContext) {
 2261    init_test(cx, |_| {});
 2262    let mut cx = EditorTestContext::new(cx).await;
 2263
 2264    let line_height = cx.update_editor(|editor, window, cx| {
 2265        editor.set_vertical_scroll_margin(2, cx);
 2266        editor
 2267            .style()
 2268            .unwrap()
 2269            .text
 2270            .line_height_in_pixels(window.rem_size())
 2271    });
 2272    let window = cx.window;
 2273    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2274
 2275    cx.set_state(
 2276        r#"ˇone
 2277            two
 2278            three
 2279            four
 2280            five
 2281            six
 2282            seven
 2283            eight
 2284            nine
 2285            ten
 2286        "#,
 2287    );
 2288    cx.update_editor(|editor, window, cx| {
 2289        assert_eq!(
 2290            editor.snapshot(window, cx).scroll_position(),
 2291            gpui::Point::new(0., 0.0)
 2292        );
 2293    });
 2294
 2295    // Add a cursor below the visible area. Since both cursors cannot fit
 2296    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2297    // allows the vertical scroll margin below that cursor.
 2298    cx.update_editor(|editor, window, cx| {
 2299        editor.change_selections(Default::default(), window, cx, |selections| {
 2300            selections.select_ranges([
 2301                Point::new(0, 0)..Point::new(0, 0),
 2302                Point::new(6, 0)..Point::new(6, 0),
 2303            ]);
 2304        })
 2305    });
 2306    cx.update_editor(|editor, window, cx| {
 2307        assert_eq!(
 2308            editor.snapshot(window, cx).scroll_position(),
 2309            gpui::Point::new(0., 3.0)
 2310        );
 2311    });
 2312
 2313    // Move down. The editor cursor scrolls down to track the newest cursor.
 2314    cx.update_editor(|editor, window, cx| {
 2315        editor.move_down(&Default::default(), window, cx);
 2316    });
 2317    cx.update_editor(|editor, window, cx| {
 2318        assert_eq!(
 2319            editor.snapshot(window, cx).scroll_position(),
 2320            gpui::Point::new(0., 4.0)
 2321        );
 2322    });
 2323
 2324    // Add a cursor above the visible area. Since both cursors fit on screen,
 2325    // the editor scrolls to show both.
 2326    cx.update_editor(|editor, window, cx| {
 2327        editor.change_selections(Default::default(), window, cx, |selections| {
 2328            selections.select_ranges([
 2329                Point::new(1, 0)..Point::new(1, 0),
 2330                Point::new(6, 0)..Point::new(6, 0),
 2331            ]);
 2332        })
 2333    });
 2334    cx.update_editor(|editor, window, cx| {
 2335        assert_eq!(
 2336            editor.snapshot(window, cx).scroll_position(),
 2337            gpui::Point::new(0., 1.0)
 2338        );
 2339    });
 2340}
 2341
 2342#[gpui::test]
 2343async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2344    init_test(cx, |_| {});
 2345    let mut cx = EditorTestContext::new(cx).await;
 2346
 2347    let line_height = cx.editor(|editor, window, _cx| {
 2348        editor
 2349            .style()
 2350            .unwrap()
 2351            .text
 2352            .line_height_in_pixels(window.rem_size())
 2353    });
 2354    let window = cx.window;
 2355    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2356    cx.set_state(
 2357        &r#"
 2358        ˇone
 2359        two
 2360        threeˇ
 2361        four
 2362        five
 2363        six
 2364        seven
 2365        eight
 2366        nine
 2367        ten
 2368        "#
 2369        .unindent(),
 2370    );
 2371
 2372    cx.update_editor(|editor, window, cx| {
 2373        editor.move_page_down(&MovePageDown::default(), window, cx)
 2374    });
 2375    cx.assert_editor_state(
 2376        &r#"
 2377        one
 2378        two
 2379        three
 2380        ˇfour
 2381        five
 2382        sixˇ
 2383        seven
 2384        eight
 2385        nine
 2386        ten
 2387        "#
 2388        .unindent(),
 2389    );
 2390
 2391    cx.update_editor(|editor, window, cx| {
 2392        editor.move_page_down(&MovePageDown::default(), window, cx)
 2393    });
 2394    cx.assert_editor_state(
 2395        &r#"
 2396        one
 2397        two
 2398        three
 2399        four
 2400        five
 2401        six
 2402        ˇseven
 2403        eight
 2404        nineˇ
 2405        ten
 2406        "#
 2407        .unindent(),
 2408    );
 2409
 2410    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2411    cx.assert_editor_state(
 2412        &r#"
 2413        one
 2414        two
 2415        three
 2416        ˇfour
 2417        five
 2418        sixˇ
 2419        seven
 2420        eight
 2421        nine
 2422        ten
 2423        "#
 2424        .unindent(),
 2425    );
 2426
 2427    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2428    cx.assert_editor_state(
 2429        &r#"
 2430        ˇone
 2431        two
 2432        threeˇ
 2433        four
 2434        five
 2435        six
 2436        seven
 2437        eight
 2438        nine
 2439        ten
 2440        "#
 2441        .unindent(),
 2442    );
 2443
 2444    // Test select collapsing
 2445    cx.update_editor(|editor, window, cx| {
 2446        editor.move_page_down(&MovePageDown::default(), window, cx);
 2447        editor.move_page_down(&MovePageDown::default(), window, cx);
 2448        editor.move_page_down(&MovePageDown::default(), window, cx);
 2449    });
 2450    cx.assert_editor_state(
 2451        &r#"
 2452        one
 2453        two
 2454        three
 2455        four
 2456        five
 2457        six
 2458        seven
 2459        eight
 2460        nine
 2461        ˇten
 2462        ˇ"#
 2463        .unindent(),
 2464    );
 2465}
 2466
 2467#[gpui::test]
 2468async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2469    init_test(cx, |_| {});
 2470    let mut cx = EditorTestContext::new(cx).await;
 2471    cx.set_state("one «two threeˇ» four");
 2472    cx.update_editor(|editor, window, cx| {
 2473        editor.delete_to_beginning_of_line(
 2474            &DeleteToBeginningOfLine {
 2475                stop_at_indent: false,
 2476            },
 2477            window,
 2478            cx,
 2479        );
 2480        assert_eq!(editor.text(cx), " four");
 2481    });
 2482}
 2483
 2484#[gpui::test]
 2485async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2486    init_test(cx, |_| {});
 2487
 2488    let mut cx = EditorTestContext::new(cx).await;
 2489
 2490    // For an empty selection, the preceding word fragment is deleted.
 2491    // For non-empty selections, only selected characters are deleted.
 2492    cx.set_state("onˇe two t«hreˇ»e four");
 2493    cx.update_editor(|editor, window, cx| {
 2494        editor.delete_to_previous_word_start(
 2495            &DeleteToPreviousWordStart {
 2496                ignore_newlines: false,
 2497                ignore_brackets: false,
 2498            },
 2499            window,
 2500            cx,
 2501        );
 2502    });
 2503    cx.assert_editor_state("ˇe two tˇe four");
 2504
 2505    cx.set_state("e tˇwo te «fˇ»our");
 2506    cx.update_editor(|editor, window, cx| {
 2507        editor.delete_to_next_word_end(
 2508            &DeleteToNextWordEnd {
 2509                ignore_newlines: false,
 2510                ignore_brackets: false,
 2511            },
 2512            window,
 2513            cx,
 2514        );
 2515    });
 2516    cx.assert_editor_state("e tˇ te ˇour");
 2517}
 2518
 2519#[gpui::test]
 2520async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2521    init_test(cx, |_| {});
 2522
 2523    let mut cx = EditorTestContext::new(cx).await;
 2524
 2525    cx.set_state("here is some text    ˇwith a space");
 2526    cx.update_editor(|editor, window, cx| {
 2527        editor.delete_to_previous_word_start(
 2528            &DeleteToPreviousWordStart {
 2529                ignore_newlines: false,
 2530                ignore_brackets: true,
 2531            },
 2532            window,
 2533            cx,
 2534        );
 2535    });
 2536    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 2537    cx.assert_editor_state("here is some textˇwith a space");
 2538
 2539    cx.set_state("here is some text    ˇwith a space");
 2540    cx.update_editor(|editor, window, cx| {
 2541        editor.delete_to_previous_word_start(
 2542            &DeleteToPreviousWordStart {
 2543                ignore_newlines: false,
 2544                ignore_brackets: false,
 2545            },
 2546            window,
 2547            cx,
 2548        );
 2549    });
 2550    cx.assert_editor_state("here is some textˇwith a space");
 2551
 2552    cx.set_state("here is some textˇ    with a space");
 2553    cx.update_editor(|editor, window, cx| {
 2554        editor.delete_to_next_word_end(
 2555            &DeleteToNextWordEnd {
 2556                ignore_newlines: false,
 2557                ignore_brackets: true,
 2558            },
 2559            window,
 2560            cx,
 2561        );
 2562    });
 2563    // Same happens in the other direction.
 2564    cx.assert_editor_state("here is some textˇwith a space");
 2565
 2566    cx.set_state("here is some textˇ    with a space");
 2567    cx.update_editor(|editor, window, cx| {
 2568        editor.delete_to_next_word_end(
 2569            &DeleteToNextWordEnd {
 2570                ignore_newlines: false,
 2571                ignore_brackets: false,
 2572            },
 2573            window,
 2574            cx,
 2575        );
 2576    });
 2577    cx.assert_editor_state("here is some textˇwith a space");
 2578
 2579    cx.set_state("here is some textˇ    with a space");
 2580    cx.update_editor(|editor, window, cx| {
 2581        editor.delete_to_next_word_end(
 2582            &DeleteToNextWordEnd {
 2583                ignore_newlines: true,
 2584                ignore_brackets: false,
 2585            },
 2586            window,
 2587            cx,
 2588        );
 2589    });
 2590    cx.assert_editor_state("here is some textˇwith a space");
 2591    cx.update_editor(|editor, window, cx| {
 2592        editor.delete_to_previous_word_start(
 2593            &DeleteToPreviousWordStart {
 2594                ignore_newlines: true,
 2595                ignore_brackets: false,
 2596            },
 2597            window,
 2598            cx,
 2599        );
 2600    });
 2601    cx.assert_editor_state("here is some ˇwith a space");
 2602    cx.update_editor(|editor, window, cx| {
 2603        editor.delete_to_previous_word_start(
 2604            &DeleteToPreviousWordStart {
 2605                ignore_newlines: true,
 2606                ignore_brackets: false,
 2607            },
 2608            window,
 2609            cx,
 2610        );
 2611    });
 2612    // Single whitespaces are removed with the word behind them.
 2613    cx.assert_editor_state("here is ˇwith a space");
 2614    cx.update_editor(|editor, window, cx| {
 2615        editor.delete_to_previous_word_start(
 2616            &DeleteToPreviousWordStart {
 2617                ignore_newlines: true,
 2618                ignore_brackets: false,
 2619            },
 2620            window,
 2621            cx,
 2622        );
 2623    });
 2624    cx.assert_editor_state("here ˇwith a space");
 2625    cx.update_editor(|editor, window, cx| {
 2626        editor.delete_to_previous_word_start(
 2627            &DeleteToPreviousWordStart {
 2628                ignore_newlines: true,
 2629                ignore_brackets: false,
 2630            },
 2631            window,
 2632            cx,
 2633        );
 2634    });
 2635    cx.assert_editor_state("ˇwith a space");
 2636    cx.update_editor(|editor, window, cx| {
 2637        editor.delete_to_previous_word_start(
 2638            &DeleteToPreviousWordStart {
 2639                ignore_newlines: true,
 2640                ignore_brackets: false,
 2641            },
 2642            window,
 2643            cx,
 2644        );
 2645    });
 2646    cx.assert_editor_state("ˇwith a space");
 2647    cx.update_editor(|editor, window, cx| {
 2648        editor.delete_to_next_word_end(
 2649            &DeleteToNextWordEnd {
 2650                ignore_newlines: true,
 2651                ignore_brackets: false,
 2652            },
 2653            window,
 2654            cx,
 2655        );
 2656    });
 2657    // Same happens in the other direction.
 2658    cx.assert_editor_state("ˇ a space");
 2659    cx.update_editor(|editor, window, cx| {
 2660        editor.delete_to_next_word_end(
 2661            &DeleteToNextWordEnd {
 2662                ignore_newlines: true,
 2663                ignore_brackets: false,
 2664            },
 2665            window,
 2666            cx,
 2667        );
 2668    });
 2669    cx.assert_editor_state("ˇ space");
 2670    cx.update_editor(|editor, window, cx| {
 2671        editor.delete_to_next_word_end(
 2672            &DeleteToNextWordEnd {
 2673                ignore_newlines: true,
 2674                ignore_brackets: false,
 2675            },
 2676            window,
 2677            cx,
 2678        );
 2679    });
 2680    cx.assert_editor_state("ˇ");
 2681    cx.update_editor(|editor, window, cx| {
 2682        editor.delete_to_next_word_end(
 2683            &DeleteToNextWordEnd {
 2684                ignore_newlines: true,
 2685                ignore_brackets: false,
 2686            },
 2687            window,
 2688            cx,
 2689        );
 2690    });
 2691    cx.assert_editor_state("ˇ");
 2692    cx.update_editor(|editor, window, cx| {
 2693        editor.delete_to_previous_word_start(
 2694            &DeleteToPreviousWordStart {
 2695                ignore_newlines: true,
 2696                ignore_brackets: false,
 2697            },
 2698            window,
 2699            cx,
 2700        );
 2701    });
 2702    cx.assert_editor_state("ˇ");
 2703}
 2704
 2705#[gpui::test]
 2706async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2707    init_test(cx, |_| {});
 2708
 2709    let language = Arc::new(
 2710        Language::new(
 2711            LanguageConfig {
 2712                brackets: BracketPairConfig {
 2713                    pairs: vec![
 2714                        BracketPair {
 2715                            start: "\"".to_string(),
 2716                            end: "\"".to_string(),
 2717                            close: true,
 2718                            surround: true,
 2719                            newline: false,
 2720                        },
 2721                        BracketPair {
 2722                            start: "(".to_string(),
 2723                            end: ")".to_string(),
 2724                            close: true,
 2725                            surround: true,
 2726                            newline: true,
 2727                        },
 2728                    ],
 2729                    ..BracketPairConfig::default()
 2730                },
 2731                ..LanguageConfig::default()
 2732            },
 2733            Some(tree_sitter_rust::LANGUAGE.into()),
 2734        )
 2735        .with_brackets_query(
 2736            r#"
 2737                ("(" @open ")" @close)
 2738                ("\"" @open "\"" @close)
 2739            "#,
 2740        )
 2741        .unwrap(),
 2742    );
 2743
 2744    let mut cx = EditorTestContext::new(cx).await;
 2745    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2746
 2747    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2748    cx.update_editor(|editor, window, cx| {
 2749        editor.delete_to_previous_word_start(
 2750            &DeleteToPreviousWordStart {
 2751                ignore_newlines: true,
 2752                ignore_brackets: false,
 2753            },
 2754            window,
 2755            cx,
 2756        );
 2757    });
 2758    // Deletion stops before brackets if asked to not ignore them.
 2759    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 2760    cx.update_editor(|editor, window, cx| {
 2761        editor.delete_to_previous_word_start(
 2762            &DeleteToPreviousWordStart {
 2763                ignore_newlines: true,
 2764                ignore_brackets: false,
 2765            },
 2766            window,
 2767            cx,
 2768        );
 2769    });
 2770    // Deletion has to remove a single bracket and then stop again.
 2771    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 2772
 2773    cx.update_editor(|editor, window, cx| {
 2774        editor.delete_to_previous_word_start(
 2775            &DeleteToPreviousWordStart {
 2776                ignore_newlines: true,
 2777                ignore_brackets: false,
 2778            },
 2779            window,
 2780            cx,
 2781        );
 2782    });
 2783    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 2784
 2785    cx.update_editor(|editor, window, cx| {
 2786        editor.delete_to_previous_word_start(
 2787            &DeleteToPreviousWordStart {
 2788                ignore_newlines: true,
 2789                ignore_brackets: false,
 2790            },
 2791            window,
 2792            cx,
 2793        );
 2794    });
 2795    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2796
 2797    cx.update_editor(|editor, window, cx| {
 2798        editor.delete_to_previous_word_start(
 2799            &DeleteToPreviousWordStart {
 2800                ignore_newlines: true,
 2801                ignore_brackets: false,
 2802            },
 2803            window,
 2804            cx,
 2805        );
 2806    });
 2807    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2808
 2809    cx.update_editor(|editor, window, cx| {
 2810        editor.delete_to_next_word_end(
 2811            &DeleteToNextWordEnd {
 2812                ignore_newlines: true,
 2813                ignore_brackets: false,
 2814            },
 2815            window,
 2816            cx,
 2817        );
 2818    });
 2819    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 2820    cx.assert_editor_state(r#"ˇ");"#);
 2821
 2822    cx.update_editor(|editor, window, cx| {
 2823        editor.delete_to_next_word_end(
 2824            &DeleteToNextWordEnd {
 2825                ignore_newlines: true,
 2826                ignore_brackets: false,
 2827            },
 2828            window,
 2829            cx,
 2830        );
 2831    });
 2832    cx.assert_editor_state(r#"ˇ"#);
 2833
 2834    cx.update_editor(|editor, window, cx| {
 2835        editor.delete_to_next_word_end(
 2836            &DeleteToNextWordEnd {
 2837                ignore_newlines: true,
 2838                ignore_brackets: false,
 2839            },
 2840            window,
 2841            cx,
 2842        );
 2843    });
 2844    cx.assert_editor_state(r#"ˇ"#);
 2845
 2846    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2847    cx.update_editor(|editor, window, cx| {
 2848        editor.delete_to_previous_word_start(
 2849            &DeleteToPreviousWordStart {
 2850                ignore_newlines: true,
 2851                ignore_brackets: true,
 2852            },
 2853            window,
 2854            cx,
 2855        );
 2856    });
 2857    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 2858}
 2859
 2860#[gpui::test]
 2861fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2862    init_test(cx, |_| {});
 2863
 2864    let editor = cx.add_window(|window, cx| {
 2865        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2866        build_editor(buffer, window, cx)
 2867    });
 2868    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2869        ignore_newlines: false,
 2870        ignore_brackets: false,
 2871    };
 2872    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2873        ignore_newlines: true,
 2874        ignore_brackets: false,
 2875    };
 2876
 2877    _ = editor.update(cx, |editor, window, cx| {
 2878        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2879            s.select_display_ranges([
 2880                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2881            ])
 2882        });
 2883        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2884        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2885        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2886        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2887        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2888        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2889        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2890        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2891        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2892        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2893        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2894        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2895    });
 2896}
 2897
 2898#[gpui::test]
 2899fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2900    init_test(cx, |_| {});
 2901
 2902    let editor = cx.add_window(|window, cx| {
 2903        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2904        build_editor(buffer, window, cx)
 2905    });
 2906    let del_to_next_word_end = DeleteToNextWordEnd {
 2907        ignore_newlines: false,
 2908        ignore_brackets: false,
 2909    };
 2910    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2911        ignore_newlines: true,
 2912        ignore_brackets: false,
 2913    };
 2914
 2915    _ = editor.update(cx, |editor, window, cx| {
 2916        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2917            s.select_display_ranges([
 2918                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2919            ])
 2920        });
 2921        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2922        assert_eq!(
 2923            editor.buffer.read(cx).read(cx).text(),
 2924            "one\n   two\nthree\n   four"
 2925        );
 2926        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2927        assert_eq!(
 2928            editor.buffer.read(cx).read(cx).text(),
 2929            "\n   two\nthree\n   four"
 2930        );
 2931        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2932        assert_eq!(
 2933            editor.buffer.read(cx).read(cx).text(),
 2934            "two\nthree\n   four"
 2935        );
 2936        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2937        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2938        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2939        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2940        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2941        assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
 2942        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2943        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2944    });
 2945}
 2946
 2947#[gpui::test]
 2948fn test_newline(cx: &mut TestAppContext) {
 2949    init_test(cx, |_| {});
 2950
 2951    let editor = cx.add_window(|window, cx| {
 2952        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2953        build_editor(buffer, window, cx)
 2954    });
 2955
 2956    _ = editor.update(cx, |editor, window, cx| {
 2957        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2958            s.select_display_ranges([
 2959                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2960                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2961                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2962            ])
 2963        });
 2964
 2965        editor.newline(&Newline, window, cx);
 2966        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2967    });
 2968}
 2969
 2970#[gpui::test]
 2971fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2972    init_test(cx, |_| {});
 2973
 2974    let editor = cx.add_window(|window, cx| {
 2975        let buffer = MultiBuffer::build_simple(
 2976            "
 2977                a
 2978                b(
 2979                    X
 2980                )
 2981                c(
 2982                    X
 2983                )
 2984            "
 2985            .unindent()
 2986            .as_str(),
 2987            cx,
 2988        );
 2989        let mut editor = build_editor(buffer, window, cx);
 2990        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2991            s.select_ranges([
 2992                Point::new(2, 4)..Point::new(2, 5),
 2993                Point::new(5, 4)..Point::new(5, 5),
 2994            ])
 2995        });
 2996        editor
 2997    });
 2998
 2999    _ = editor.update(cx, |editor, window, cx| {
 3000        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3001        editor.buffer.update(cx, |buffer, cx| {
 3002            buffer.edit(
 3003                [
 3004                    (Point::new(1, 2)..Point::new(3, 0), ""),
 3005                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3006                ],
 3007                None,
 3008                cx,
 3009            );
 3010            assert_eq!(
 3011                buffer.read(cx).text(),
 3012                "
 3013                    a
 3014                    b()
 3015                    c()
 3016                "
 3017                .unindent()
 3018            );
 3019        });
 3020        assert_eq!(
 3021            editor.selections.ranges(cx),
 3022            &[
 3023                Point::new(1, 2)..Point::new(1, 2),
 3024                Point::new(2, 2)..Point::new(2, 2),
 3025            ],
 3026        );
 3027
 3028        editor.newline(&Newline, window, cx);
 3029        assert_eq!(
 3030            editor.text(cx),
 3031            "
 3032                a
 3033                b(
 3034                )
 3035                c(
 3036                )
 3037            "
 3038            .unindent()
 3039        );
 3040
 3041        // The selections are moved after the inserted newlines
 3042        assert_eq!(
 3043            editor.selections.ranges(cx),
 3044            &[
 3045                Point::new(2, 0)..Point::new(2, 0),
 3046                Point::new(4, 0)..Point::new(4, 0),
 3047            ],
 3048        );
 3049    });
 3050}
 3051
 3052#[gpui::test]
 3053async fn test_newline_above(cx: &mut TestAppContext) {
 3054    init_test(cx, |settings| {
 3055        settings.defaults.tab_size = NonZeroU32::new(4)
 3056    });
 3057
 3058    let language = Arc::new(
 3059        Language::new(
 3060            LanguageConfig::default(),
 3061            Some(tree_sitter_rust::LANGUAGE.into()),
 3062        )
 3063        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3064        .unwrap(),
 3065    );
 3066
 3067    let mut cx = EditorTestContext::new(cx).await;
 3068    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3069    cx.set_state(indoc! {"
 3070        const a: ˇA = (
 3071 3072                «const_functionˇ»(ˇ),
 3073                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3074 3075        ˇ);ˇ
 3076    "});
 3077
 3078    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3079    cx.assert_editor_state(indoc! {"
 3080        ˇ
 3081        const a: A = (
 3082            ˇ
 3083            (
 3084                ˇ
 3085                ˇ
 3086                const_function(),
 3087                ˇ
 3088                ˇ
 3089                ˇ
 3090                ˇ
 3091                something_else,
 3092                ˇ
 3093            )
 3094            ˇ
 3095            ˇ
 3096        );
 3097    "});
 3098}
 3099
 3100#[gpui::test]
 3101async fn test_newline_below(cx: &mut TestAppContext) {
 3102    init_test(cx, |settings| {
 3103        settings.defaults.tab_size = NonZeroU32::new(4)
 3104    });
 3105
 3106    let language = Arc::new(
 3107        Language::new(
 3108            LanguageConfig::default(),
 3109            Some(tree_sitter_rust::LANGUAGE.into()),
 3110        )
 3111        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3112        .unwrap(),
 3113    );
 3114
 3115    let mut cx = EditorTestContext::new(cx).await;
 3116    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3117    cx.set_state(indoc! {"
 3118        const a: ˇA = (
 3119 3120                «const_functionˇ»(ˇ),
 3121                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3122 3123        ˇ);ˇ
 3124    "});
 3125
 3126    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3127    cx.assert_editor_state(indoc! {"
 3128        const a: A = (
 3129            ˇ
 3130            (
 3131                ˇ
 3132                const_function(),
 3133                ˇ
 3134                ˇ
 3135                something_else,
 3136                ˇ
 3137                ˇ
 3138                ˇ
 3139                ˇ
 3140            )
 3141            ˇ
 3142        );
 3143        ˇ
 3144        ˇ
 3145    "});
 3146}
 3147
 3148#[gpui::test]
 3149async fn test_newline_comments(cx: &mut TestAppContext) {
 3150    init_test(cx, |settings| {
 3151        settings.defaults.tab_size = NonZeroU32::new(4)
 3152    });
 3153
 3154    let language = Arc::new(Language::new(
 3155        LanguageConfig {
 3156            line_comments: vec!["// ".into()],
 3157            ..LanguageConfig::default()
 3158        },
 3159        None,
 3160    ));
 3161    {
 3162        let mut cx = EditorTestContext::new(cx).await;
 3163        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3164        cx.set_state(indoc! {"
 3165        // Fooˇ
 3166    "});
 3167
 3168        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3169        cx.assert_editor_state(indoc! {"
 3170        // Foo
 3171        // ˇ
 3172    "});
 3173        // Ensure that we add comment prefix when existing line contains space
 3174        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3175        cx.assert_editor_state(
 3176            indoc! {"
 3177        // Foo
 3178        //s
 3179        // ˇ
 3180    "}
 3181            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3182            .as_str(),
 3183        );
 3184        // Ensure that we add comment prefix when existing line does not contain space
 3185        cx.set_state(indoc! {"
 3186        // Foo
 3187        //ˇ
 3188    "});
 3189        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3190        cx.assert_editor_state(indoc! {"
 3191        // Foo
 3192        //
 3193        // ˇ
 3194    "});
 3195        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3196        cx.set_state(indoc! {"
 3197        ˇ// Foo
 3198    "});
 3199        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3200        cx.assert_editor_state(indoc! {"
 3201
 3202        ˇ// Foo
 3203    "});
 3204    }
 3205    // Ensure that comment continuations can be disabled.
 3206    update_test_language_settings(cx, |settings| {
 3207        settings.defaults.extend_comment_on_newline = Some(false);
 3208    });
 3209    let mut cx = EditorTestContext::new(cx).await;
 3210    cx.set_state(indoc! {"
 3211        // Fooˇ
 3212    "});
 3213    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3214    cx.assert_editor_state(indoc! {"
 3215        // Foo
 3216        ˇ
 3217    "});
 3218}
 3219
 3220#[gpui::test]
 3221async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3222    init_test(cx, |settings| {
 3223        settings.defaults.tab_size = NonZeroU32::new(4)
 3224    });
 3225
 3226    let language = Arc::new(Language::new(
 3227        LanguageConfig {
 3228            line_comments: vec!["// ".into(), "/// ".into()],
 3229            ..LanguageConfig::default()
 3230        },
 3231        None,
 3232    ));
 3233    {
 3234        let mut cx = EditorTestContext::new(cx).await;
 3235        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3236        cx.set_state(indoc! {"
 3237        //ˇ
 3238    "});
 3239        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3240        cx.assert_editor_state(indoc! {"
 3241        //
 3242        // ˇ
 3243    "});
 3244
 3245        cx.set_state(indoc! {"
 3246        ///ˇ
 3247    "});
 3248        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3249        cx.assert_editor_state(indoc! {"
 3250        ///
 3251        /// ˇ
 3252    "});
 3253    }
 3254}
 3255
 3256#[gpui::test]
 3257async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3258    init_test(cx, |settings| {
 3259        settings.defaults.tab_size = NonZeroU32::new(4)
 3260    });
 3261
 3262    let language = Arc::new(
 3263        Language::new(
 3264            LanguageConfig {
 3265                documentation_comment: Some(language::BlockCommentConfig {
 3266                    start: "/**".into(),
 3267                    end: "*/".into(),
 3268                    prefix: "* ".into(),
 3269                    tab_size: 1,
 3270                }),
 3271
 3272                ..LanguageConfig::default()
 3273            },
 3274            Some(tree_sitter_rust::LANGUAGE.into()),
 3275        )
 3276        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3277        .unwrap(),
 3278    );
 3279
 3280    {
 3281        let mut cx = EditorTestContext::new(cx).await;
 3282        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3283        cx.set_state(indoc! {"
 3284        /**ˇ
 3285    "});
 3286
 3287        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3288        cx.assert_editor_state(indoc! {"
 3289        /**
 3290         * ˇ
 3291    "});
 3292        // Ensure that if cursor is before the comment start,
 3293        // we do not actually insert a comment prefix.
 3294        cx.set_state(indoc! {"
 3295        ˇ/**
 3296    "});
 3297        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3298        cx.assert_editor_state(indoc! {"
 3299
 3300        ˇ/**
 3301    "});
 3302        // Ensure that if cursor is between it doesn't add comment prefix.
 3303        cx.set_state(indoc! {"
 3304        /*ˇ*
 3305    "});
 3306        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3307        cx.assert_editor_state(indoc! {"
 3308        /*
 3309        ˇ*
 3310    "});
 3311        // Ensure that if suffix exists on same line after cursor it adds new line.
 3312        cx.set_state(indoc! {"
 3313        /**ˇ*/
 3314    "});
 3315        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3316        cx.assert_editor_state(indoc! {"
 3317        /**
 3318         * ˇ
 3319         */
 3320    "});
 3321        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3322        cx.set_state(indoc! {"
 3323        /**ˇ */
 3324    "});
 3325        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3326        cx.assert_editor_state(indoc! {"
 3327        /**
 3328         * ˇ
 3329         */
 3330    "});
 3331        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3332        cx.set_state(indoc! {"
 3333        /** ˇ*/
 3334    "});
 3335        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3336        cx.assert_editor_state(
 3337            indoc! {"
 3338        /**s
 3339         * ˇ
 3340         */
 3341    "}
 3342            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3343            .as_str(),
 3344        );
 3345        // Ensure that delimiter space is preserved when newline on already
 3346        // spaced delimiter.
 3347        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3348        cx.assert_editor_state(
 3349            indoc! {"
 3350        /**s
 3351         *s
 3352         * ˇ
 3353         */
 3354    "}
 3355            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3356            .as_str(),
 3357        );
 3358        // Ensure that delimiter space is preserved when space is not
 3359        // on existing delimiter.
 3360        cx.set_state(indoc! {"
 3361        /**
 3362 3363         */
 3364    "});
 3365        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3366        cx.assert_editor_state(indoc! {"
 3367        /**
 3368         *
 3369         * ˇ
 3370         */
 3371    "});
 3372        // Ensure that if suffix exists on same line after cursor it
 3373        // doesn't add extra new line if prefix is not on same line.
 3374        cx.set_state(indoc! {"
 3375        /**
 3376        ˇ*/
 3377    "});
 3378        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3379        cx.assert_editor_state(indoc! {"
 3380        /**
 3381
 3382        ˇ*/
 3383    "});
 3384        // Ensure that it detects suffix after existing prefix.
 3385        cx.set_state(indoc! {"
 3386        /**ˇ/
 3387    "});
 3388        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3389        cx.assert_editor_state(indoc! {"
 3390        /**
 3391        ˇ/
 3392    "});
 3393        // Ensure that if suffix exists on same line before
 3394        // cursor it does not add comment prefix.
 3395        cx.set_state(indoc! {"
 3396        /** */ˇ
 3397    "});
 3398        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3399        cx.assert_editor_state(indoc! {"
 3400        /** */
 3401        ˇ
 3402    "});
 3403        // Ensure that if suffix exists on same line before
 3404        // cursor it does not add comment prefix.
 3405        cx.set_state(indoc! {"
 3406        /**
 3407         *
 3408         */ˇ
 3409    "});
 3410        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3411        cx.assert_editor_state(indoc! {"
 3412        /**
 3413         *
 3414         */
 3415         ˇ
 3416    "});
 3417
 3418        // Ensure that inline comment followed by code
 3419        // doesn't add comment prefix on newline
 3420        cx.set_state(indoc! {"
 3421        /** */ textˇ
 3422    "});
 3423        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3424        cx.assert_editor_state(indoc! {"
 3425        /** */ text
 3426        ˇ
 3427    "});
 3428
 3429        // Ensure that text after comment end tag
 3430        // doesn't add comment prefix on newline
 3431        cx.set_state(indoc! {"
 3432        /**
 3433         *
 3434         */ˇtext
 3435    "});
 3436        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3437        cx.assert_editor_state(indoc! {"
 3438        /**
 3439         *
 3440         */
 3441         ˇtext
 3442    "});
 3443
 3444        // Ensure if not comment block it doesn't
 3445        // add comment prefix on newline
 3446        cx.set_state(indoc! {"
 3447        * textˇ
 3448    "});
 3449        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3450        cx.assert_editor_state(indoc! {"
 3451        * text
 3452        ˇ
 3453    "});
 3454    }
 3455    // Ensure that comment continuations can be disabled.
 3456    update_test_language_settings(cx, |settings| {
 3457        settings.defaults.extend_comment_on_newline = Some(false);
 3458    });
 3459    let mut cx = EditorTestContext::new(cx).await;
 3460    cx.set_state(indoc! {"
 3461        /**ˇ
 3462    "});
 3463    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3464    cx.assert_editor_state(indoc! {"
 3465        /**
 3466        ˇ
 3467    "});
 3468}
 3469
 3470#[gpui::test]
 3471async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3472    init_test(cx, |settings| {
 3473        settings.defaults.tab_size = NonZeroU32::new(4)
 3474    });
 3475
 3476    let lua_language = Arc::new(Language::new(
 3477        LanguageConfig {
 3478            line_comments: vec!["--".into()],
 3479            block_comment: Some(language::BlockCommentConfig {
 3480                start: "--[[".into(),
 3481                prefix: "".into(),
 3482                end: "]]".into(),
 3483                tab_size: 0,
 3484            }),
 3485            ..LanguageConfig::default()
 3486        },
 3487        None,
 3488    ));
 3489
 3490    let mut cx = EditorTestContext::new(cx).await;
 3491    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3492
 3493    // Line with line comment should extend
 3494    cx.set_state(indoc! {"
 3495        --ˇ
 3496    "});
 3497    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3498    cx.assert_editor_state(indoc! {"
 3499        --
 3500        --ˇ
 3501    "});
 3502
 3503    // Line with block comment that matches line comment should not extend
 3504    cx.set_state(indoc! {"
 3505        --[[ˇ
 3506    "});
 3507    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3508    cx.assert_editor_state(indoc! {"
 3509        --[[
 3510        ˇ
 3511    "});
 3512}
 3513
 3514#[gpui::test]
 3515fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3516    init_test(cx, |_| {});
 3517
 3518    let editor = cx.add_window(|window, cx| {
 3519        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3520        let mut editor = build_editor(buffer, window, cx);
 3521        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3522            s.select_ranges([3..4, 11..12, 19..20])
 3523        });
 3524        editor
 3525    });
 3526
 3527    _ = editor.update(cx, |editor, window, cx| {
 3528        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3529        editor.buffer.update(cx, |buffer, cx| {
 3530            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3531            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3532        });
 3533        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3534
 3535        editor.insert("Z", window, cx);
 3536        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3537
 3538        // The selections are moved after the inserted characters
 3539        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3540    });
 3541}
 3542
 3543#[gpui::test]
 3544async fn test_tab(cx: &mut TestAppContext) {
 3545    init_test(cx, |settings| {
 3546        settings.defaults.tab_size = NonZeroU32::new(3)
 3547    });
 3548
 3549    let mut cx = EditorTestContext::new(cx).await;
 3550    cx.set_state(indoc! {"
 3551        ˇabˇc
 3552        ˇ🏀ˇ🏀ˇefg
 3553 3554    "});
 3555    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3556    cx.assert_editor_state(indoc! {"
 3557           ˇab ˇc
 3558           ˇ🏀  ˇ🏀  ˇefg
 3559        d  ˇ
 3560    "});
 3561
 3562    cx.set_state(indoc! {"
 3563        a
 3564        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3565    "});
 3566    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3567    cx.assert_editor_state(indoc! {"
 3568        a
 3569           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3570    "});
 3571}
 3572
 3573#[gpui::test]
 3574async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3575    init_test(cx, |_| {});
 3576
 3577    let mut cx = EditorTestContext::new(cx).await;
 3578    let language = Arc::new(
 3579        Language::new(
 3580            LanguageConfig::default(),
 3581            Some(tree_sitter_rust::LANGUAGE.into()),
 3582        )
 3583        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3584        .unwrap(),
 3585    );
 3586    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3587
 3588    // test when all cursors are not at suggested indent
 3589    // then simply move to their suggested indent location
 3590    cx.set_state(indoc! {"
 3591        const a: B = (
 3592            c(
 3593        ˇ
 3594        ˇ    )
 3595        );
 3596    "});
 3597    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3598    cx.assert_editor_state(indoc! {"
 3599        const a: B = (
 3600            c(
 3601                ˇ
 3602            ˇ)
 3603        );
 3604    "});
 3605
 3606    // test cursor already at suggested indent not moving when
 3607    // other cursors are yet to reach their suggested indents
 3608    cx.set_state(indoc! {"
 3609        ˇ
 3610        const a: B = (
 3611            c(
 3612                d(
 3613        ˇ
 3614                )
 3615        ˇ
 3616        ˇ    )
 3617        );
 3618    "});
 3619    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3620    cx.assert_editor_state(indoc! {"
 3621        ˇ
 3622        const a: B = (
 3623            c(
 3624                d(
 3625                    ˇ
 3626                )
 3627                ˇ
 3628            ˇ)
 3629        );
 3630    "});
 3631    // test when all cursors are at suggested indent then tab is inserted
 3632    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3633    cx.assert_editor_state(indoc! {"
 3634            ˇ
 3635        const a: B = (
 3636            c(
 3637                d(
 3638                        ˇ
 3639                )
 3640                    ˇ
 3641                ˇ)
 3642        );
 3643    "});
 3644
 3645    // test when current indent is less than suggested indent,
 3646    // we adjust line to match suggested indent and move cursor to it
 3647    //
 3648    // when no other cursor is at word boundary, all of them should move
 3649    cx.set_state(indoc! {"
 3650        const a: B = (
 3651            c(
 3652                d(
 3653        ˇ
 3654        ˇ   )
 3655        ˇ   )
 3656        );
 3657    "});
 3658    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3659    cx.assert_editor_state(indoc! {"
 3660        const a: B = (
 3661            c(
 3662                d(
 3663                    ˇ
 3664                ˇ)
 3665            ˇ)
 3666        );
 3667    "});
 3668
 3669    // test when current indent is less than suggested indent,
 3670    // we adjust line to match suggested indent and move cursor to it
 3671    //
 3672    // when some other cursor is at word boundary, it should not move
 3673    cx.set_state(indoc! {"
 3674        const a: B = (
 3675            c(
 3676                d(
 3677        ˇ
 3678        ˇ   )
 3679           ˇ)
 3680        );
 3681    "});
 3682    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3683    cx.assert_editor_state(indoc! {"
 3684        const a: B = (
 3685            c(
 3686                d(
 3687                    ˇ
 3688                ˇ)
 3689            ˇ)
 3690        );
 3691    "});
 3692
 3693    // test when current indent is more than suggested indent,
 3694    // we just move cursor to current indent instead of suggested indent
 3695    //
 3696    // when no other cursor is at word boundary, all of them should move
 3697    cx.set_state(indoc! {"
 3698        const a: B = (
 3699            c(
 3700                d(
 3701        ˇ
 3702        ˇ                )
 3703        ˇ   )
 3704        );
 3705    "});
 3706    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3707    cx.assert_editor_state(indoc! {"
 3708        const a: B = (
 3709            c(
 3710                d(
 3711                    ˇ
 3712                        ˇ)
 3713            ˇ)
 3714        );
 3715    "});
 3716    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3717    cx.assert_editor_state(indoc! {"
 3718        const a: B = (
 3719            c(
 3720                d(
 3721                        ˇ
 3722                            ˇ)
 3723                ˇ)
 3724        );
 3725    "});
 3726
 3727    // test when current indent is more than suggested indent,
 3728    // we just move cursor to current indent instead of suggested indent
 3729    //
 3730    // when some other cursor is at word boundary, it doesn't move
 3731    cx.set_state(indoc! {"
 3732        const a: B = (
 3733            c(
 3734                d(
 3735        ˇ
 3736        ˇ                )
 3737            ˇ)
 3738        );
 3739    "});
 3740    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3741    cx.assert_editor_state(indoc! {"
 3742        const a: B = (
 3743            c(
 3744                d(
 3745                    ˇ
 3746                        ˇ)
 3747            ˇ)
 3748        );
 3749    "});
 3750
 3751    // handle auto-indent when there are multiple cursors on the same line
 3752    cx.set_state(indoc! {"
 3753        const a: B = (
 3754            c(
 3755        ˇ    ˇ
 3756        ˇ    )
 3757        );
 3758    "});
 3759    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3760    cx.assert_editor_state(indoc! {"
 3761        const a: B = (
 3762            c(
 3763                ˇ
 3764            ˇ)
 3765        );
 3766    "});
 3767}
 3768
 3769#[gpui::test]
 3770async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3771    init_test(cx, |settings| {
 3772        settings.defaults.tab_size = NonZeroU32::new(3)
 3773    });
 3774
 3775    let mut cx = EditorTestContext::new(cx).await;
 3776    cx.set_state(indoc! {"
 3777         ˇ
 3778        \t ˇ
 3779        \t  ˇ
 3780        \t   ˇ
 3781         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3782    "});
 3783
 3784    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3785    cx.assert_editor_state(indoc! {"
 3786           ˇ
 3787        \t   ˇ
 3788        \t   ˇ
 3789        \t      ˇ
 3790         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3791    "});
 3792}
 3793
 3794#[gpui::test]
 3795async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3796    init_test(cx, |settings| {
 3797        settings.defaults.tab_size = NonZeroU32::new(4)
 3798    });
 3799
 3800    let language = Arc::new(
 3801        Language::new(
 3802            LanguageConfig::default(),
 3803            Some(tree_sitter_rust::LANGUAGE.into()),
 3804        )
 3805        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3806        .unwrap(),
 3807    );
 3808
 3809    let mut cx = EditorTestContext::new(cx).await;
 3810    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3811    cx.set_state(indoc! {"
 3812        fn a() {
 3813            if b {
 3814        \t ˇc
 3815            }
 3816        }
 3817    "});
 3818
 3819    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3820    cx.assert_editor_state(indoc! {"
 3821        fn a() {
 3822            if b {
 3823                ˇc
 3824            }
 3825        }
 3826    "});
 3827}
 3828
 3829#[gpui::test]
 3830async fn test_indent_outdent(cx: &mut TestAppContext) {
 3831    init_test(cx, |settings| {
 3832        settings.defaults.tab_size = NonZeroU32::new(4);
 3833    });
 3834
 3835    let mut cx = EditorTestContext::new(cx).await;
 3836
 3837    cx.set_state(indoc! {"
 3838          «oneˇ» «twoˇ»
 3839        three
 3840         four
 3841    "});
 3842    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3843    cx.assert_editor_state(indoc! {"
 3844            «oneˇ» «twoˇ»
 3845        three
 3846         four
 3847    "});
 3848
 3849    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3850    cx.assert_editor_state(indoc! {"
 3851        «oneˇ» «twoˇ»
 3852        three
 3853         four
 3854    "});
 3855
 3856    // select across line ending
 3857    cx.set_state(indoc! {"
 3858        one two
 3859        t«hree
 3860        ˇ» four
 3861    "});
 3862    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3863    cx.assert_editor_state(indoc! {"
 3864        one two
 3865            t«hree
 3866        ˇ» four
 3867    "});
 3868
 3869    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3870    cx.assert_editor_state(indoc! {"
 3871        one two
 3872        t«hree
 3873        ˇ» four
 3874    "});
 3875
 3876    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3877    cx.set_state(indoc! {"
 3878        one two
 3879        ˇthree
 3880            four
 3881    "});
 3882    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3883    cx.assert_editor_state(indoc! {"
 3884        one two
 3885            ˇthree
 3886            four
 3887    "});
 3888
 3889    cx.set_state(indoc! {"
 3890        one two
 3891        ˇ    three
 3892            four
 3893    "});
 3894    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3895    cx.assert_editor_state(indoc! {"
 3896        one two
 3897        ˇthree
 3898            four
 3899    "});
 3900}
 3901
 3902#[gpui::test]
 3903async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3904    // This is a regression test for issue #33761
 3905    init_test(cx, |_| {});
 3906
 3907    let mut cx = EditorTestContext::new(cx).await;
 3908    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3909    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3910
 3911    cx.set_state(
 3912        r#"ˇ#     ingress:
 3913ˇ#         api:
 3914ˇ#             enabled: false
 3915ˇ#             pathType: Prefix
 3916ˇ#           console:
 3917ˇ#               enabled: false
 3918ˇ#               pathType: Prefix
 3919"#,
 3920    );
 3921
 3922    // Press tab to indent all lines
 3923    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3924
 3925    cx.assert_editor_state(
 3926        r#"    ˇ#     ingress:
 3927    ˇ#         api:
 3928    ˇ#             enabled: false
 3929    ˇ#             pathType: Prefix
 3930    ˇ#           console:
 3931    ˇ#               enabled: false
 3932    ˇ#               pathType: Prefix
 3933"#,
 3934    );
 3935}
 3936
 3937#[gpui::test]
 3938async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3939    // This is a test to make sure our fix for issue #33761 didn't break anything
 3940    init_test(cx, |_| {});
 3941
 3942    let mut cx = EditorTestContext::new(cx).await;
 3943    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3944    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3945
 3946    cx.set_state(
 3947        r#"ˇingress:
 3948ˇ  api:
 3949ˇ    enabled: false
 3950ˇ    pathType: Prefix
 3951"#,
 3952    );
 3953
 3954    // Press tab to indent all lines
 3955    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3956
 3957    cx.assert_editor_state(
 3958        r#"ˇingress:
 3959    ˇapi:
 3960        ˇenabled: false
 3961        ˇpathType: Prefix
 3962"#,
 3963    );
 3964}
 3965
 3966#[gpui::test]
 3967async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3968    init_test(cx, |settings| {
 3969        settings.defaults.hard_tabs = Some(true);
 3970    });
 3971
 3972    let mut cx = EditorTestContext::new(cx).await;
 3973
 3974    // select two ranges on one line
 3975    cx.set_state(indoc! {"
 3976        «oneˇ» «twoˇ»
 3977        three
 3978        four
 3979    "});
 3980    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3981    cx.assert_editor_state(indoc! {"
 3982        \t«oneˇ» «twoˇ»
 3983        three
 3984        four
 3985    "});
 3986    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3987    cx.assert_editor_state(indoc! {"
 3988        \t\t«oneˇ» «twoˇ»
 3989        three
 3990        four
 3991    "});
 3992    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3993    cx.assert_editor_state(indoc! {"
 3994        \t«oneˇ» «twoˇ»
 3995        three
 3996        four
 3997    "});
 3998    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3999    cx.assert_editor_state(indoc! {"
 4000        «oneˇ» «twoˇ»
 4001        three
 4002        four
 4003    "});
 4004
 4005    // select across a line ending
 4006    cx.set_state(indoc! {"
 4007        one two
 4008        t«hree
 4009        ˇ»four
 4010    "});
 4011    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4012    cx.assert_editor_state(indoc! {"
 4013        one two
 4014        \tt«hree
 4015        ˇ»four
 4016    "});
 4017    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4018    cx.assert_editor_state(indoc! {"
 4019        one two
 4020        \t\tt«hree
 4021        ˇ»four
 4022    "});
 4023    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4024    cx.assert_editor_state(indoc! {"
 4025        one two
 4026        \tt«hree
 4027        ˇ»four
 4028    "});
 4029    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4030    cx.assert_editor_state(indoc! {"
 4031        one two
 4032        t«hree
 4033        ˇ»four
 4034    "});
 4035
 4036    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4037    cx.set_state(indoc! {"
 4038        one two
 4039        ˇthree
 4040        four
 4041    "});
 4042    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4043    cx.assert_editor_state(indoc! {"
 4044        one two
 4045        ˇthree
 4046        four
 4047    "});
 4048    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4049    cx.assert_editor_state(indoc! {"
 4050        one two
 4051        \tˇthree
 4052        four
 4053    "});
 4054    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4055    cx.assert_editor_state(indoc! {"
 4056        one two
 4057        ˇthree
 4058        four
 4059    "});
 4060}
 4061
 4062#[gpui::test]
 4063fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4064    init_test(cx, |settings| {
 4065        settings.languages.0.extend([
 4066            (
 4067                "TOML".into(),
 4068                LanguageSettingsContent {
 4069                    tab_size: NonZeroU32::new(2),
 4070                    ..Default::default()
 4071                },
 4072            ),
 4073            (
 4074                "Rust".into(),
 4075                LanguageSettingsContent {
 4076                    tab_size: NonZeroU32::new(4),
 4077                    ..Default::default()
 4078                },
 4079            ),
 4080        ]);
 4081    });
 4082
 4083    let toml_language = Arc::new(Language::new(
 4084        LanguageConfig {
 4085            name: "TOML".into(),
 4086            ..Default::default()
 4087        },
 4088        None,
 4089    ));
 4090    let rust_language = Arc::new(Language::new(
 4091        LanguageConfig {
 4092            name: "Rust".into(),
 4093            ..Default::default()
 4094        },
 4095        None,
 4096    ));
 4097
 4098    let toml_buffer =
 4099        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4100    let rust_buffer =
 4101        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4102    let multibuffer = cx.new(|cx| {
 4103        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4104        multibuffer.push_excerpts(
 4105            toml_buffer.clone(),
 4106            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4107            cx,
 4108        );
 4109        multibuffer.push_excerpts(
 4110            rust_buffer.clone(),
 4111            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4112            cx,
 4113        );
 4114        multibuffer
 4115    });
 4116
 4117    cx.add_window(|window, cx| {
 4118        let mut editor = build_editor(multibuffer, window, cx);
 4119
 4120        assert_eq!(
 4121            editor.text(cx),
 4122            indoc! {"
 4123                a = 1
 4124                b = 2
 4125
 4126                const c: usize = 3;
 4127            "}
 4128        );
 4129
 4130        select_ranges(
 4131            &mut editor,
 4132            indoc! {"
 4133                «aˇ» = 1
 4134                b = 2
 4135
 4136                «const c:ˇ» usize = 3;
 4137            "},
 4138            window,
 4139            cx,
 4140        );
 4141
 4142        editor.tab(&Tab, window, cx);
 4143        assert_text_with_selections(
 4144            &mut editor,
 4145            indoc! {"
 4146                  «aˇ» = 1
 4147                b = 2
 4148
 4149                    «const c:ˇ» usize = 3;
 4150            "},
 4151            cx,
 4152        );
 4153        editor.backtab(&Backtab, window, cx);
 4154        assert_text_with_selections(
 4155            &mut editor,
 4156            indoc! {"
 4157                «aˇ» = 1
 4158                b = 2
 4159
 4160                «const c:ˇ» usize = 3;
 4161            "},
 4162            cx,
 4163        );
 4164
 4165        editor
 4166    });
 4167}
 4168
 4169#[gpui::test]
 4170async fn test_backspace(cx: &mut TestAppContext) {
 4171    init_test(cx, |_| {});
 4172
 4173    let mut cx = EditorTestContext::new(cx).await;
 4174
 4175    // Basic backspace
 4176    cx.set_state(indoc! {"
 4177        onˇe two three
 4178        fou«rˇ» five six
 4179        seven «ˇeight nine
 4180        »ten
 4181    "});
 4182    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4183    cx.assert_editor_state(indoc! {"
 4184        oˇe two three
 4185        fouˇ five six
 4186        seven ˇten
 4187    "});
 4188
 4189    // Test backspace inside and around indents
 4190    cx.set_state(indoc! {"
 4191        zero
 4192            ˇone
 4193                ˇtwo
 4194            ˇ ˇ ˇ  three
 4195        ˇ  ˇ  four
 4196    "});
 4197    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4198    cx.assert_editor_state(indoc! {"
 4199        zero
 4200        ˇone
 4201            ˇtwo
 4202        ˇ  threeˇ  four
 4203    "});
 4204}
 4205
 4206#[gpui::test]
 4207async fn test_delete(cx: &mut TestAppContext) {
 4208    init_test(cx, |_| {});
 4209
 4210    let mut cx = EditorTestContext::new(cx).await;
 4211    cx.set_state(indoc! {"
 4212        onˇe two three
 4213        fou«rˇ» five six
 4214        seven «ˇeight nine
 4215        »ten
 4216    "});
 4217    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4218    cx.assert_editor_state(indoc! {"
 4219        onˇ two three
 4220        fouˇ five six
 4221        seven ˇten
 4222    "});
 4223}
 4224
 4225#[gpui::test]
 4226fn test_delete_line(cx: &mut TestAppContext) {
 4227    init_test(cx, |_| {});
 4228
 4229    let editor = cx.add_window(|window, cx| {
 4230        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4231        build_editor(buffer, window, cx)
 4232    });
 4233    _ = editor.update(cx, |editor, window, cx| {
 4234        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4235            s.select_display_ranges([
 4236                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4237                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4238                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4239            ])
 4240        });
 4241        editor.delete_line(&DeleteLine, window, cx);
 4242        assert_eq!(editor.display_text(cx), "ghi");
 4243        assert_eq!(
 4244            editor.selections.display_ranges(cx),
 4245            vec![
 4246                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4247                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 4248            ]
 4249        );
 4250    });
 4251
 4252    let editor = cx.add_window(|window, cx| {
 4253        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4254        build_editor(buffer, window, cx)
 4255    });
 4256    _ = editor.update(cx, |editor, window, cx| {
 4257        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4258            s.select_display_ranges([
 4259                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4260            ])
 4261        });
 4262        editor.delete_line(&DeleteLine, window, cx);
 4263        assert_eq!(editor.display_text(cx), "ghi\n");
 4264        assert_eq!(
 4265            editor.selections.display_ranges(cx),
 4266            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4267        );
 4268    });
 4269}
 4270
 4271#[gpui::test]
 4272fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4273    init_test(cx, |_| {});
 4274
 4275    cx.add_window(|window, cx| {
 4276        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4277        let mut editor = build_editor(buffer.clone(), window, cx);
 4278        let buffer = buffer.read(cx).as_singleton().unwrap();
 4279
 4280        assert_eq!(
 4281            editor.selections.ranges::<Point>(cx),
 4282            &[Point::new(0, 0)..Point::new(0, 0)]
 4283        );
 4284
 4285        // When on single line, replace newline at end by space
 4286        editor.join_lines(&JoinLines, window, cx);
 4287        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4288        assert_eq!(
 4289            editor.selections.ranges::<Point>(cx),
 4290            &[Point::new(0, 3)..Point::new(0, 3)]
 4291        );
 4292
 4293        // When multiple lines are selected, remove newlines that are spanned by the selection
 4294        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4295            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4296        });
 4297        editor.join_lines(&JoinLines, window, cx);
 4298        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4299        assert_eq!(
 4300            editor.selections.ranges::<Point>(cx),
 4301            &[Point::new(0, 11)..Point::new(0, 11)]
 4302        );
 4303
 4304        // Undo should be transactional
 4305        editor.undo(&Undo, window, cx);
 4306        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4307        assert_eq!(
 4308            editor.selections.ranges::<Point>(cx),
 4309            &[Point::new(0, 5)..Point::new(2, 2)]
 4310        );
 4311
 4312        // When joining an empty line don't insert a space
 4313        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4314            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4315        });
 4316        editor.join_lines(&JoinLines, window, cx);
 4317        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4318        assert_eq!(
 4319            editor.selections.ranges::<Point>(cx),
 4320            [Point::new(2, 3)..Point::new(2, 3)]
 4321        );
 4322
 4323        // We can remove trailing newlines
 4324        editor.join_lines(&JoinLines, window, cx);
 4325        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4326        assert_eq!(
 4327            editor.selections.ranges::<Point>(cx),
 4328            [Point::new(2, 3)..Point::new(2, 3)]
 4329        );
 4330
 4331        // We don't blow up on the last line
 4332        editor.join_lines(&JoinLines, window, cx);
 4333        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4334        assert_eq!(
 4335            editor.selections.ranges::<Point>(cx),
 4336            [Point::new(2, 3)..Point::new(2, 3)]
 4337        );
 4338
 4339        // reset to test indentation
 4340        editor.buffer.update(cx, |buffer, cx| {
 4341            buffer.edit(
 4342                [
 4343                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4344                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4345                ],
 4346                None,
 4347                cx,
 4348            )
 4349        });
 4350
 4351        // We remove any leading spaces
 4352        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4353        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4354            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4355        });
 4356        editor.join_lines(&JoinLines, window, cx);
 4357        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4358
 4359        // We don't insert a space for a line containing only spaces
 4360        editor.join_lines(&JoinLines, window, cx);
 4361        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4362
 4363        // We ignore any leading tabs
 4364        editor.join_lines(&JoinLines, window, cx);
 4365        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4366
 4367        editor
 4368    });
 4369}
 4370
 4371#[gpui::test]
 4372fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4373    init_test(cx, |_| {});
 4374
 4375    cx.add_window(|window, cx| {
 4376        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4377        let mut editor = build_editor(buffer.clone(), window, cx);
 4378        let buffer = buffer.read(cx).as_singleton().unwrap();
 4379
 4380        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4381            s.select_ranges([
 4382                Point::new(0, 2)..Point::new(1, 1),
 4383                Point::new(1, 2)..Point::new(1, 2),
 4384                Point::new(3, 1)..Point::new(3, 2),
 4385            ])
 4386        });
 4387
 4388        editor.join_lines(&JoinLines, window, cx);
 4389        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4390
 4391        assert_eq!(
 4392            editor.selections.ranges::<Point>(cx),
 4393            [
 4394                Point::new(0, 7)..Point::new(0, 7),
 4395                Point::new(1, 3)..Point::new(1, 3)
 4396            ]
 4397        );
 4398        editor
 4399    });
 4400}
 4401
 4402#[gpui::test]
 4403async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4404    init_test(cx, |_| {});
 4405
 4406    let mut cx = EditorTestContext::new(cx).await;
 4407
 4408    let diff_base = r#"
 4409        Line 0
 4410        Line 1
 4411        Line 2
 4412        Line 3
 4413        "#
 4414    .unindent();
 4415
 4416    cx.set_state(
 4417        &r#"
 4418        ˇLine 0
 4419        Line 1
 4420        Line 2
 4421        Line 3
 4422        "#
 4423        .unindent(),
 4424    );
 4425
 4426    cx.set_head_text(&diff_base);
 4427    executor.run_until_parked();
 4428
 4429    // Join lines
 4430    cx.update_editor(|editor, window, cx| {
 4431        editor.join_lines(&JoinLines, window, cx);
 4432    });
 4433    executor.run_until_parked();
 4434
 4435    cx.assert_editor_state(
 4436        &r#"
 4437        Line 0ˇ Line 1
 4438        Line 2
 4439        Line 3
 4440        "#
 4441        .unindent(),
 4442    );
 4443    // Join again
 4444    cx.update_editor(|editor, window, cx| {
 4445        editor.join_lines(&JoinLines, window, cx);
 4446    });
 4447    executor.run_until_parked();
 4448
 4449    cx.assert_editor_state(
 4450        &r#"
 4451        Line 0 Line 1ˇ Line 2
 4452        Line 3
 4453        "#
 4454        .unindent(),
 4455    );
 4456}
 4457
 4458#[gpui::test]
 4459async fn test_custom_newlines_cause_no_false_positive_diffs(
 4460    executor: BackgroundExecutor,
 4461    cx: &mut TestAppContext,
 4462) {
 4463    init_test(cx, |_| {});
 4464    let mut cx = EditorTestContext::new(cx).await;
 4465    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4466    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4467    executor.run_until_parked();
 4468
 4469    cx.update_editor(|editor, window, cx| {
 4470        let snapshot = editor.snapshot(window, cx);
 4471        assert_eq!(
 4472            snapshot
 4473                .buffer_snapshot
 4474                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 4475                .collect::<Vec<_>>(),
 4476            Vec::new(),
 4477            "Should not have any diffs for files with custom newlines"
 4478        );
 4479    });
 4480}
 4481
 4482#[gpui::test]
 4483async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4484    init_test(cx, |_| {});
 4485
 4486    let mut cx = EditorTestContext::new(cx).await;
 4487
 4488    // Test sort_lines_case_insensitive()
 4489    cx.set_state(indoc! {"
 4490        «z
 4491        y
 4492        x
 4493        Z
 4494        Y
 4495        Xˇ»
 4496    "});
 4497    cx.update_editor(|e, window, cx| {
 4498        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4499    });
 4500    cx.assert_editor_state(indoc! {"
 4501        «x
 4502        X
 4503        y
 4504        Y
 4505        z
 4506        Zˇ»
 4507    "});
 4508
 4509    // Test sort_lines_by_length()
 4510    //
 4511    // Demonstrates:
 4512    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4513    // - sort is stable
 4514    cx.set_state(indoc! {"
 4515        «123
 4516        æ
 4517        12
 4518 4519        1
 4520        æˇ»
 4521    "});
 4522    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4523    cx.assert_editor_state(indoc! {"
 4524        «æ
 4525 4526        1
 4527        æ
 4528        12
 4529        123ˇ»
 4530    "});
 4531
 4532    // Test reverse_lines()
 4533    cx.set_state(indoc! {"
 4534        «5
 4535        4
 4536        3
 4537        2
 4538        1ˇ»
 4539    "});
 4540    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4541    cx.assert_editor_state(indoc! {"
 4542        «1
 4543        2
 4544        3
 4545        4
 4546        5ˇ»
 4547    "});
 4548
 4549    // Skip testing shuffle_line()
 4550
 4551    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4552    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4553
 4554    // Don't manipulate when cursor is on single line, but expand the selection
 4555    cx.set_state(indoc! {"
 4556        ddˇdd
 4557        ccc
 4558        bb
 4559        a
 4560    "});
 4561    cx.update_editor(|e, window, cx| {
 4562        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4563    });
 4564    cx.assert_editor_state(indoc! {"
 4565        «ddddˇ»
 4566        ccc
 4567        bb
 4568        a
 4569    "});
 4570
 4571    // Basic manipulate case
 4572    // Start selection moves to column 0
 4573    // End of selection shrinks to fit shorter line
 4574    cx.set_state(indoc! {"
 4575        dd«d
 4576        ccc
 4577        bb
 4578        aaaaaˇ»
 4579    "});
 4580    cx.update_editor(|e, window, cx| {
 4581        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4582    });
 4583    cx.assert_editor_state(indoc! {"
 4584        «aaaaa
 4585        bb
 4586        ccc
 4587        dddˇ»
 4588    "});
 4589
 4590    // Manipulate case with newlines
 4591    cx.set_state(indoc! {"
 4592        dd«d
 4593        ccc
 4594
 4595        bb
 4596        aaaaa
 4597
 4598        ˇ»
 4599    "});
 4600    cx.update_editor(|e, window, cx| {
 4601        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4602    });
 4603    cx.assert_editor_state(indoc! {"
 4604        «
 4605
 4606        aaaaa
 4607        bb
 4608        ccc
 4609        dddˇ»
 4610
 4611    "});
 4612
 4613    // Adding new line
 4614    cx.set_state(indoc! {"
 4615        aa«a
 4616        bbˇ»b
 4617    "});
 4618    cx.update_editor(|e, window, cx| {
 4619        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4620    });
 4621    cx.assert_editor_state(indoc! {"
 4622        «aaa
 4623        bbb
 4624        added_lineˇ»
 4625    "});
 4626
 4627    // Removing line
 4628    cx.set_state(indoc! {"
 4629        aa«a
 4630        bbbˇ»
 4631    "});
 4632    cx.update_editor(|e, window, cx| {
 4633        e.manipulate_immutable_lines(window, cx, |lines| {
 4634            lines.pop();
 4635        })
 4636    });
 4637    cx.assert_editor_state(indoc! {"
 4638        «aaaˇ»
 4639    "});
 4640
 4641    // Removing all lines
 4642    cx.set_state(indoc! {"
 4643        aa«a
 4644        bbbˇ»
 4645    "});
 4646    cx.update_editor(|e, window, cx| {
 4647        e.manipulate_immutable_lines(window, cx, |lines| {
 4648            lines.drain(..);
 4649        })
 4650    });
 4651    cx.assert_editor_state(indoc! {"
 4652        ˇ
 4653    "});
 4654}
 4655
 4656#[gpui::test]
 4657async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4658    init_test(cx, |_| {});
 4659
 4660    let mut cx = EditorTestContext::new(cx).await;
 4661
 4662    // Consider continuous selection as single selection
 4663    cx.set_state(indoc! {"
 4664        Aaa«aa
 4665        cˇ»c«c
 4666        bb
 4667        aaaˇ»aa
 4668    "});
 4669    cx.update_editor(|e, window, cx| {
 4670        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4671    });
 4672    cx.assert_editor_state(indoc! {"
 4673        «Aaaaa
 4674        ccc
 4675        bb
 4676        aaaaaˇ»
 4677    "});
 4678
 4679    cx.set_state(indoc! {"
 4680        Aaa«aa
 4681        cˇ»c«c
 4682        bb
 4683        aaaˇ»aa
 4684    "});
 4685    cx.update_editor(|e, window, cx| {
 4686        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4687    });
 4688    cx.assert_editor_state(indoc! {"
 4689        «Aaaaa
 4690        ccc
 4691        bbˇ»
 4692    "});
 4693
 4694    // Consider non continuous selection as distinct dedup operations
 4695    cx.set_state(indoc! {"
 4696        «aaaaa
 4697        bb
 4698        aaaaa
 4699        aaaaaˇ»
 4700
 4701        aaa«aaˇ»
 4702    "});
 4703    cx.update_editor(|e, window, cx| {
 4704        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4705    });
 4706    cx.assert_editor_state(indoc! {"
 4707        «aaaaa
 4708        bbˇ»
 4709
 4710        «aaaaaˇ»
 4711    "});
 4712}
 4713
 4714#[gpui::test]
 4715async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4716    init_test(cx, |_| {});
 4717
 4718    let mut cx = EditorTestContext::new(cx).await;
 4719
 4720    cx.set_state(indoc! {"
 4721        «Aaa
 4722        aAa
 4723        Aaaˇ»
 4724    "});
 4725    cx.update_editor(|e, window, cx| {
 4726        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4727    });
 4728    cx.assert_editor_state(indoc! {"
 4729        «Aaa
 4730        aAaˇ»
 4731    "});
 4732
 4733    cx.set_state(indoc! {"
 4734        «Aaa
 4735        aAa
 4736        aaAˇ»
 4737    "});
 4738    cx.update_editor(|e, window, cx| {
 4739        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4740    });
 4741    cx.assert_editor_state(indoc! {"
 4742        «Aaaˇ»
 4743    "});
 4744}
 4745
 4746#[gpui::test]
 4747async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 4748    init_test(cx, |_| {});
 4749
 4750    let mut cx = EditorTestContext::new(cx).await;
 4751
 4752    let js_language = Arc::new(Language::new(
 4753        LanguageConfig {
 4754            name: "JavaScript".into(),
 4755            wrap_characters: Some(language::WrapCharactersConfig {
 4756                start_prefix: "<".into(),
 4757                start_suffix: ">".into(),
 4758                end_prefix: "</".into(),
 4759                end_suffix: ">".into(),
 4760            }),
 4761            ..LanguageConfig::default()
 4762        },
 4763        None,
 4764    ));
 4765
 4766    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4767
 4768    cx.set_state(indoc! {"
 4769        «testˇ»
 4770    "});
 4771    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4772    cx.assert_editor_state(indoc! {"
 4773        <«ˇ»>test</«ˇ»>
 4774    "});
 4775
 4776    cx.set_state(indoc! {"
 4777        «test
 4778         testˇ»
 4779    "});
 4780    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4781    cx.assert_editor_state(indoc! {"
 4782        <«ˇ»>test
 4783         test</«ˇ»>
 4784    "});
 4785
 4786    cx.set_state(indoc! {"
 4787        teˇst
 4788    "});
 4789    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4790    cx.assert_editor_state(indoc! {"
 4791        te<«ˇ»></«ˇ»>st
 4792    "});
 4793}
 4794
 4795#[gpui::test]
 4796async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 4797    init_test(cx, |_| {});
 4798
 4799    let mut cx = EditorTestContext::new(cx).await;
 4800
 4801    let js_language = Arc::new(Language::new(
 4802        LanguageConfig {
 4803            name: "JavaScript".into(),
 4804            wrap_characters: Some(language::WrapCharactersConfig {
 4805                start_prefix: "<".into(),
 4806                start_suffix: ">".into(),
 4807                end_prefix: "</".into(),
 4808                end_suffix: ">".into(),
 4809            }),
 4810            ..LanguageConfig::default()
 4811        },
 4812        None,
 4813    ));
 4814
 4815    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4816
 4817    cx.set_state(indoc! {"
 4818        «testˇ»
 4819        «testˇ» «testˇ»
 4820        «testˇ»
 4821    "});
 4822    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4823    cx.assert_editor_state(indoc! {"
 4824        <«ˇ»>test</«ˇ»>
 4825        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 4826        <«ˇ»>test</«ˇ»>
 4827    "});
 4828
 4829    cx.set_state(indoc! {"
 4830        «test
 4831         testˇ»
 4832        «test
 4833         testˇ»
 4834    "});
 4835    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4836    cx.assert_editor_state(indoc! {"
 4837        <«ˇ»>test
 4838         test</«ˇ»>
 4839        <«ˇ»>test
 4840         test</«ˇ»>
 4841    "});
 4842}
 4843
 4844#[gpui::test]
 4845async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 4846    init_test(cx, |_| {});
 4847
 4848    let mut cx = EditorTestContext::new(cx).await;
 4849
 4850    let plaintext_language = Arc::new(Language::new(
 4851        LanguageConfig {
 4852            name: "Plain Text".into(),
 4853            ..LanguageConfig::default()
 4854        },
 4855        None,
 4856    ));
 4857
 4858    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 4859
 4860    cx.set_state(indoc! {"
 4861        «testˇ»
 4862    "});
 4863    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4864    cx.assert_editor_state(indoc! {"
 4865      «testˇ»
 4866    "});
 4867}
 4868
 4869#[gpui::test]
 4870async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4871    init_test(cx, |_| {});
 4872
 4873    let mut cx = EditorTestContext::new(cx).await;
 4874
 4875    // Manipulate with multiple selections on a single line
 4876    cx.set_state(indoc! {"
 4877        dd«dd
 4878        cˇ»c«c
 4879        bb
 4880        aaaˇ»aa
 4881    "});
 4882    cx.update_editor(|e, window, cx| {
 4883        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4884    });
 4885    cx.assert_editor_state(indoc! {"
 4886        «aaaaa
 4887        bb
 4888        ccc
 4889        ddddˇ»
 4890    "});
 4891
 4892    // Manipulate with multiple disjoin selections
 4893    cx.set_state(indoc! {"
 4894 4895        4
 4896        3
 4897        2
 4898        1ˇ»
 4899
 4900        dd«dd
 4901        ccc
 4902        bb
 4903        aaaˇ»aa
 4904    "});
 4905    cx.update_editor(|e, window, cx| {
 4906        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4907    });
 4908    cx.assert_editor_state(indoc! {"
 4909        «1
 4910        2
 4911        3
 4912        4
 4913        5ˇ»
 4914
 4915        «aaaaa
 4916        bb
 4917        ccc
 4918        ddddˇ»
 4919    "});
 4920
 4921    // Adding lines on each selection
 4922    cx.set_state(indoc! {"
 4923 4924        1ˇ»
 4925
 4926        bb«bb
 4927        aaaˇ»aa
 4928    "});
 4929    cx.update_editor(|e, window, cx| {
 4930        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4931    });
 4932    cx.assert_editor_state(indoc! {"
 4933        «2
 4934        1
 4935        added lineˇ»
 4936
 4937        «bbbb
 4938        aaaaa
 4939        added lineˇ»
 4940    "});
 4941
 4942    // Removing lines on each selection
 4943    cx.set_state(indoc! {"
 4944 4945        1ˇ»
 4946
 4947        bb«bb
 4948        aaaˇ»aa
 4949    "});
 4950    cx.update_editor(|e, window, cx| {
 4951        e.manipulate_immutable_lines(window, cx, |lines| {
 4952            lines.pop();
 4953        })
 4954    });
 4955    cx.assert_editor_state(indoc! {"
 4956        «2ˇ»
 4957
 4958        «bbbbˇ»
 4959    "});
 4960}
 4961
 4962#[gpui::test]
 4963async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 4964    init_test(cx, |settings| {
 4965        settings.defaults.tab_size = NonZeroU32::new(3)
 4966    });
 4967
 4968    let mut cx = EditorTestContext::new(cx).await;
 4969
 4970    // MULTI SELECTION
 4971    // Ln.1 "«" tests empty lines
 4972    // Ln.9 tests just leading whitespace
 4973    cx.set_state(indoc! {"
 4974        «
 4975        abc                 // No indentationˇ»
 4976        «\tabc              // 1 tabˇ»
 4977        \t\tabc «      ˇ»   // 2 tabs
 4978        \t ab«c             // Tab followed by space
 4979         \tabc              // Space followed by tab (3 spaces should be the result)
 4980        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4981           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 4982        \t
 4983        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4984    "});
 4985    cx.update_editor(|e, window, cx| {
 4986        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4987    });
 4988    cx.assert_editor_state(
 4989        indoc! {"
 4990            «
 4991            abc                 // No indentation
 4992               abc              // 1 tab
 4993                  abc          // 2 tabs
 4994                abc             // Tab followed by space
 4995               abc              // Space followed by tab (3 spaces should be the result)
 4996                           abc   // Mixed indentation (tab conversion depends on the column)
 4997               abc         // Already space indented
 4998               ·
 4999               abc\tdef          // Only the leading tab is manipulatedˇ»
 5000        "}
 5001        .replace("·", "")
 5002        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5003    );
 5004
 5005    // Test on just a few lines, the others should remain unchanged
 5006    // Only lines (3, 5, 10, 11) should change
 5007    cx.set_state(
 5008        indoc! {"
 5009            ·
 5010            abc                 // No indentation
 5011            \tabcˇ               // 1 tab
 5012            \t\tabc             // 2 tabs
 5013            \t abcˇ              // Tab followed by space
 5014             \tabc              // Space followed by tab (3 spaces should be the result)
 5015            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5016               abc              // Already space indented
 5017            «\t
 5018            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5019        "}
 5020        .replace("·", "")
 5021        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5022    );
 5023    cx.update_editor(|e, window, cx| {
 5024        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5025    });
 5026    cx.assert_editor_state(
 5027        indoc! {"
 5028            ·
 5029            abc                 // No indentation
 5030            «   abc               // 1 tabˇ»
 5031            \t\tabc             // 2 tabs
 5032            «    abc              // Tab followed by spaceˇ»
 5033             \tabc              // Space followed by tab (3 spaces should be the result)
 5034            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5035               abc              // Already space indented
 5036            «   ·
 5037               abc\tdef          // Only the leading tab is manipulatedˇ»
 5038        "}
 5039        .replace("·", "")
 5040        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5041    );
 5042
 5043    // SINGLE SELECTION
 5044    // Ln.1 "«" tests empty lines
 5045    // Ln.9 tests just leading whitespace
 5046    cx.set_state(indoc! {"
 5047        «
 5048        abc                 // No indentation
 5049        \tabc               // 1 tab
 5050        \t\tabc             // 2 tabs
 5051        \t abc              // Tab followed by space
 5052         \tabc              // Space followed by tab (3 spaces should be the result)
 5053        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5054           abc              // Already space indented
 5055        \t
 5056        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5057    "});
 5058    cx.update_editor(|e, window, cx| {
 5059        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5060    });
 5061    cx.assert_editor_state(
 5062        indoc! {"
 5063            «
 5064            abc                 // No indentation
 5065               abc               // 1 tab
 5066                  abc             // 2 tabs
 5067                abc              // Tab followed by space
 5068               abc              // Space followed by tab (3 spaces should be the result)
 5069                           abc   // Mixed indentation (tab conversion depends on the column)
 5070               abc              // Already space indented
 5071               ·
 5072               abc\tdef          // Only the leading tab is manipulatedˇ»
 5073        "}
 5074        .replace("·", "")
 5075        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5076    );
 5077}
 5078
 5079#[gpui::test]
 5080async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5081    init_test(cx, |settings| {
 5082        settings.defaults.tab_size = NonZeroU32::new(3)
 5083    });
 5084
 5085    let mut cx = EditorTestContext::new(cx).await;
 5086
 5087    // MULTI SELECTION
 5088    // Ln.1 "«" tests empty lines
 5089    // Ln.11 tests just leading whitespace
 5090    cx.set_state(indoc! {"
 5091        «
 5092        abˇ»ˇc                 // No indentation
 5093         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5094          abc  «             // 2 spaces (< 3 so dont convert)
 5095           abc              // 3 spaces (convert)
 5096             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5097        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5098        «\t abc              // Tab followed by space
 5099         \tabc              // Space followed by tab (should be consumed due to tab)
 5100        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5101           \tˇ»  «\t
 5102           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5103    "});
 5104    cx.update_editor(|e, window, cx| {
 5105        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5106    });
 5107    cx.assert_editor_state(indoc! {"
 5108        «
 5109        abc                 // No indentation
 5110         abc                // 1 space (< 3 so dont convert)
 5111          abc               // 2 spaces (< 3 so dont convert)
 5112        \tabc              // 3 spaces (convert)
 5113        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5114        \t\t\tabc           // Already tab indented
 5115        \t abc              // Tab followed by space
 5116        \tabc              // Space followed by tab (should be consumed due to tab)
 5117        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5118        \t\t\t
 5119        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5120    "});
 5121
 5122    // Test on just a few lines, the other should remain unchanged
 5123    // Only lines (4, 8, 11, 12) should change
 5124    cx.set_state(
 5125        indoc! {"
 5126            ·
 5127            abc                 // No indentation
 5128             abc                // 1 space (< 3 so dont convert)
 5129              abc               // 2 spaces (< 3 so dont convert)
 5130            «   abc              // 3 spaces (convert)ˇ»
 5131                 abc            // 5 spaces (1 tab + 2 spaces)
 5132            \t\t\tabc           // Already tab indented
 5133            \t abc              // Tab followed by space
 5134             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5135               \t\t  \tabc      // Mixed indentation
 5136            \t \t  \t   \tabc   // Mixed indentation
 5137               \t  \tˇ
 5138            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5139        "}
 5140        .replace("·", "")
 5141        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5142    );
 5143    cx.update_editor(|e, window, cx| {
 5144        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5145    });
 5146    cx.assert_editor_state(
 5147        indoc! {"
 5148            ·
 5149            abc                 // No indentation
 5150             abc                // 1 space (< 3 so dont convert)
 5151              abc               // 2 spaces (< 3 so dont convert)
 5152            «\tabc              // 3 spaces (convert)ˇ»
 5153                 abc            // 5 spaces (1 tab + 2 spaces)
 5154            \t\t\tabc           // Already tab indented
 5155            \t abc              // Tab followed by space
 5156            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5157               \t\t  \tabc      // Mixed indentation
 5158            \t \t  \t   \tabc   // Mixed indentation
 5159            «\t\t\t
 5160            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5161        "}
 5162        .replace("·", "")
 5163        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5164    );
 5165
 5166    // SINGLE SELECTION
 5167    // Ln.1 "«" tests empty lines
 5168    // Ln.11 tests just leading whitespace
 5169    cx.set_state(indoc! {"
 5170        «
 5171        abc                 // No indentation
 5172         abc                // 1 space (< 3 so dont convert)
 5173          abc               // 2 spaces (< 3 so dont convert)
 5174           abc              // 3 spaces (convert)
 5175             abc            // 5 spaces (1 tab + 2 spaces)
 5176        \t\t\tabc           // Already tab indented
 5177        \t abc              // Tab followed by space
 5178         \tabc              // Space followed by tab (should be consumed due to tab)
 5179        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5180           \t  \t
 5181           abc   \t         // Only the leading spaces should be convertedˇ»
 5182    "});
 5183    cx.update_editor(|e, window, cx| {
 5184        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5185    });
 5186    cx.assert_editor_state(indoc! {"
 5187        «
 5188        abc                 // No indentation
 5189         abc                // 1 space (< 3 so dont convert)
 5190          abc               // 2 spaces (< 3 so dont convert)
 5191        \tabc              // 3 spaces (convert)
 5192        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5193        \t\t\tabc           // Already tab indented
 5194        \t abc              // Tab followed by space
 5195        \tabc              // Space followed by tab (should be consumed due to tab)
 5196        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5197        \t\t\t
 5198        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5199    "});
 5200}
 5201
 5202#[gpui::test]
 5203async fn test_toggle_case(cx: &mut TestAppContext) {
 5204    init_test(cx, |_| {});
 5205
 5206    let mut cx = EditorTestContext::new(cx).await;
 5207
 5208    // If all lower case -> upper case
 5209    cx.set_state(indoc! {"
 5210        «hello worldˇ»
 5211    "});
 5212    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5213    cx.assert_editor_state(indoc! {"
 5214        «HELLO WORLDˇ»
 5215    "});
 5216
 5217    // If all upper case -> lower case
 5218    cx.set_state(indoc! {"
 5219        «HELLO WORLDˇ»
 5220    "});
 5221    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5222    cx.assert_editor_state(indoc! {"
 5223        «hello worldˇ»
 5224    "});
 5225
 5226    // If any upper case characters are identified -> lower case
 5227    // This matches JetBrains IDEs
 5228    cx.set_state(indoc! {"
 5229        «hEllo worldˇ»
 5230    "});
 5231    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5232    cx.assert_editor_state(indoc! {"
 5233        «hello worldˇ»
 5234    "});
 5235}
 5236
 5237#[gpui::test]
 5238async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5239    init_test(cx, |_| {});
 5240
 5241    let mut cx = EditorTestContext::new(cx).await;
 5242
 5243    cx.set_state(indoc! {"
 5244        «implement-windows-supportˇ»
 5245    "});
 5246    cx.update_editor(|e, window, cx| {
 5247        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5248    });
 5249    cx.assert_editor_state(indoc! {"
 5250        «Implement windows supportˇ»
 5251    "});
 5252}
 5253
 5254#[gpui::test]
 5255async fn test_manipulate_text(cx: &mut TestAppContext) {
 5256    init_test(cx, |_| {});
 5257
 5258    let mut cx = EditorTestContext::new(cx).await;
 5259
 5260    // Test convert_to_upper_case()
 5261    cx.set_state(indoc! {"
 5262        «hello worldˇ»
 5263    "});
 5264    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5265    cx.assert_editor_state(indoc! {"
 5266        «HELLO WORLDˇ»
 5267    "});
 5268
 5269    // Test convert_to_lower_case()
 5270    cx.set_state(indoc! {"
 5271        «HELLO WORLDˇ»
 5272    "});
 5273    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5274    cx.assert_editor_state(indoc! {"
 5275        «hello worldˇ»
 5276    "});
 5277
 5278    // Test multiple line, single selection case
 5279    cx.set_state(indoc! {"
 5280        «The quick brown
 5281        fox jumps over
 5282        the lazy dogˇ»
 5283    "});
 5284    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5285    cx.assert_editor_state(indoc! {"
 5286        «The Quick Brown
 5287        Fox Jumps Over
 5288        The Lazy Dogˇ»
 5289    "});
 5290
 5291    // Test multiple line, single selection case
 5292    cx.set_state(indoc! {"
 5293        «The quick brown
 5294        fox jumps over
 5295        the lazy dogˇ»
 5296    "});
 5297    cx.update_editor(|e, window, cx| {
 5298        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5299    });
 5300    cx.assert_editor_state(indoc! {"
 5301        «TheQuickBrown
 5302        FoxJumpsOver
 5303        TheLazyDogˇ»
 5304    "});
 5305
 5306    // From here on out, test more complex cases of manipulate_text()
 5307
 5308    // Test no selection case - should affect words cursors are in
 5309    // Cursor at beginning, middle, and end of word
 5310    cx.set_state(indoc! {"
 5311        ˇhello big beauˇtiful worldˇ
 5312    "});
 5313    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5314    cx.assert_editor_state(indoc! {"
 5315        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5316    "});
 5317
 5318    // Test multiple selections on a single line and across multiple lines
 5319    cx.set_state(indoc! {"
 5320        «Theˇ» quick «brown
 5321        foxˇ» jumps «overˇ»
 5322        the «lazyˇ» dog
 5323    "});
 5324    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5325    cx.assert_editor_state(indoc! {"
 5326        «THEˇ» quick «BROWN
 5327        FOXˇ» jumps «OVERˇ»
 5328        the «LAZYˇ» dog
 5329    "});
 5330
 5331    // Test case where text length grows
 5332    cx.set_state(indoc! {"
 5333        «tschüߡ»
 5334    "});
 5335    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5336    cx.assert_editor_state(indoc! {"
 5337        «TSCHÜSSˇ»
 5338    "});
 5339
 5340    // Test to make sure we don't crash when text shrinks
 5341    cx.set_state(indoc! {"
 5342        aaa_bbbˇ
 5343    "});
 5344    cx.update_editor(|e, window, cx| {
 5345        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5346    });
 5347    cx.assert_editor_state(indoc! {"
 5348        «aaaBbbˇ»
 5349    "});
 5350
 5351    // Test to make sure we all aware of the fact that each word can grow and shrink
 5352    // Final selections should be aware of this fact
 5353    cx.set_state(indoc! {"
 5354        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5355    "});
 5356    cx.update_editor(|e, window, cx| {
 5357        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5358    });
 5359    cx.assert_editor_state(indoc! {"
 5360        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5361    "});
 5362
 5363    cx.set_state(indoc! {"
 5364        «hElLo, WoRld!ˇ»
 5365    "});
 5366    cx.update_editor(|e, window, cx| {
 5367        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5368    });
 5369    cx.assert_editor_state(indoc! {"
 5370        «HeLlO, wOrLD!ˇ»
 5371    "});
 5372
 5373    // Test selections with `line_mode() = true`.
 5374    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 5375    cx.set_state(indoc! {"
 5376        «The quick brown
 5377        fox jumps over
 5378        tˇ»he lazy dog
 5379    "});
 5380    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5381    cx.assert_editor_state(indoc! {"
 5382        «THE QUICK BROWN
 5383        FOX JUMPS OVER
 5384        THE LAZY DOGˇ»
 5385    "});
 5386}
 5387
 5388#[gpui::test]
 5389fn test_duplicate_line(cx: &mut TestAppContext) {
 5390    init_test(cx, |_| {});
 5391
 5392    let editor = cx.add_window(|window, cx| {
 5393        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5394        build_editor(buffer, window, cx)
 5395    });
 5396    _ = editor.update(cx, |editor, window, cx| {
 5397        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5398            s.select_display_ranges([
 5399                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5400                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5401                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5402                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5403            ])
 5404        });
 5405        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5406        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5407        assert_eq!(
 5408            editor.selections.display_ranges(cx),
 5409            vec![
 5410                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5411                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5412                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5413                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5414            ]
 5415        );
 5416    });
 5417
 5418    let editor = cx.add_window(|window, cx| {
 5419        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5420        build_editor(buffer, window, cx)
 5421    });
 5422    _ = editor.update(cx, |editor, window, cx| {
 5423        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5424            s.select_display_ranges([
 5425                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5426                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5427            ])
 5428        });
 5429        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5430        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5431        assert_eq!(
 5432            editor.selections.display_ranges(cx),
 5433            vec![
 5434                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5435                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5436            ]
 5437        );
 5438    });
 5439
 5440    // With `move_upwards` the selections stay in place, except for
 5441    // the lines inserted above them
 5442    let editor = cx.add_window(|window, cx| {
 5443        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5444        build_editor(buffer, window, cx)
 5445    });
 5446    _ = editor.update(cx, |editor, window, cx| {
 5447        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5448            s.select_display_ranges([
 5449                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5450                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5451                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5452                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5453            ])
 5454        });
 5455        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5456        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5457        assert_eq!(
 5458            editor.selections.display_ranges(cx),
 5459            vec![
 5460                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5461                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5462                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5463                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5464            ]
 5465        );
 5466    });
 5467
 5468    let editor = cx.add_window(|window, cx| {
 5469        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5470        build_editor(buffer, window, cx)
 5471    });
 5472    _ = editor.update(cx, |editor, window, cx| {
 5473        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5474            s.select_display_ranges([
 5475                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5476                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5477            ])
 5478        });
 5479        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5480        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5481        assert_eq!(
 5482            editor.selections.display_ranges(cx),
 5483            vec![
 5484                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5485                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5486            ]
 5487        );
 5488    });
 5489
 5490    let editor = cx.add_window(|window, cx| {
 5491        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5492        build_editor(buffer, window, cx)
 5493    });
 5494    _ = editor.update(cx, |editor, window, cx| {
 5495        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5496            s.select_display_ranges([
 5497                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5498                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5499            ])
 5500        });
 5501        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5502        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5503        assert_eq!(
 5504            editor.selections.display_ranges(cx),
 5505            vec![
 5506                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5507                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5508            ]
 5509        );
 5510    });
 5511}
 5512
 5513#[gpui::test]
 5514fn test_move_line_up_down(cx: &mut TestAppContext) {
 5515    init_test(cx, |_| {});
 5516
 5517    let editor = cx.add_window(|window, cx| {
 5518        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5519        build_editor(buffer, window, cx)
 5520    });
 5521    _ = editor.update(cx, |editor, window, cx| {
 5522        editor.fold_creases(
 5523            vec![
 5524                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5525                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5526                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5527            ],
 5528            true,
 5529            window,
 5530            cx,
 5531        );
 5532        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5533            s.select_display_ranges([
 5534                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5535                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5536                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5537                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5538            ])
 5539        });
 5540        assert_eq!(
 5541            editor.display_text(cx),
 5542            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5543        );
 5544
 5545        editor.move_line_up(&MoveLineUp, window, cx);
 5546        assert_eq!(
 5547            editor.display_text(cx),
 5548            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5549        );
 5550        assert_eq!(
 5551            editor.selections.display_ranges(cx),
 5552            vec![
 5553                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5554                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5555                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5556                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5557            ]
 5558        );
 5559    });
 5560
 5561    _ = editor.update(cx, |editor, window, cx| {
 5562        editor.move_line_down(&MoveLineDown, window, cx);
 5563        assert_eq!(
 5564            editor.display_text(cx),
 5565            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5566        );
 5567        assert_eq!(
 5568            editor.selections.display_ranges(cx),
 5569            vec![
 5570                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5571                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5572                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5573                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5574            ]
 5575        );
 5576    });
 5577
 5578    _ = editor.update(cx, |editor, window, cx| {
 5579        editor.move_line_down(&MoveLineDown, window, cx);
 5580        assert_eq!(
 5581            editor.display_text(cx),
 5582            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5583        );
 5584        assert_eq!(
 5585            editor.selections.display_ranges(cx),
 5586            vec![
 5587                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5588                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5589                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5590                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5591            ]
 5592        );
 5593    });
 5594
 5595    _ = editor.update(cx, |editor, window, cx| {
 5596        editor.move_line_up(&MoveLineUp, window, cx);
 5597        assert_eq!(
 5598            editor.display_text(cx),
 5599            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5600        );
 5601        assert_eq!(
 5602            editor.selections.display_ranges(cx),
 5603            vec![
 5604                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5605                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5606                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5607                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5608            ]
 5609        );
 5610    });
 5611}
 5612
 5613#[gpui::test]
 5614fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5615    init_test(cx, |_| {});
 5616    let editor = cx.add_window(|window, cx| {
 5617        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5618        build_editor(buffer, window, cx)
 5619    });
 5620    _ = editor.update(cx, |editor, window, cx| {
 5621        editor.fold_creases(
 5622            vec![Crease::simple(
 5623                Point::new(6, 4)..Point::new(7, 4),
 5624                FoldPlaceholder::test(),
 5625            )],
 5626            true,
 5627            window,
 5628            cx,
 5629        );
 5630        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5631            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5632        });
 5633        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5634        editor.move_line_up(&MoveLineUp, window, cx);
 5635        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5636        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5637    });
 5638}
 5639
 5640#[gpui::test]
 5641fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5642    init_test(cx, |_| {});
 5643
 5644    let editor = cx.add_window(|window, cx| {
 5645        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5646        build_editor(buffer, window, cx)
 5647    });
 5648    _ = editor.update(cx, |editor, window, cx| {
 5649        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5650        editor.insert_blocks(
 5651            [BlockProperties {
 5652                style: BlockStyle::Fixed,
 5653                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5654                height: Some(1),
 5655                render: Arc::new(|_| div().into_any()),
 5656                priority: 0,
 5657            }],
 5658            Some(Autoscroll::fit()),
 5659            cx,
 5660        );
 5661        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5662            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5663        });
 5664        editor.move_line_down(&MoveLineDown, window, cx);
 5665    });
 5666}
 5667
 5668#[gpui::test]
 5669async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5670    init_test(cx, |_| {});
 5671
 5672    let mut cx = EditorTestContext::new(cx).await;
 5673    cx.set_state(
 5674        &"
 5675            ˇzero
 5676            one
 5677            two
 5678            three
 5679            four
 5680            five
 5681        "
 5682        .unindent(),
 5683    );
 5684
 5685    // Create a four-line block that replaces three lines of text.
 5686    cx.update_editor(|editor, window, cx| {
 5687        let snapshot = editor.snapshot(window, cx);
 5688        let snapshot = &snapshot.buffer_snapshot;
 5689        let placement = BlockPlacement::Replace(
 5690            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5691        );
 5692        editor.insert_blocks(
 5693            [BlockProperties {
 5694                placement,
 5695                height: Some(4),
 5696                style: BlockStyle::Sticky,
 5697                render: Arc::new(|_| gpui::div().into_any_element()),
 5698                priority: 0,
 5699            }],
 5700            None,
 5701            cx,
 5702        );
 5703    });
 5704
 5705    // Move down so that the cursor touches the block.
 5706    cx.update_editor(|editor, window, cx| {
 5707        editor.move_down(&Default::default(), window, cx);
 5708    });
 5709    cx.assert_editor_state(
 5710        &"
 5711            zero
 5712            «one
 5713            two
 5714            threeˇ»
 5715            four
 5716            five
 5717        "
 5718        .unindent(),
 5719    );
 5720
 5721    // Move down past the block.
 5722    cx.update_editor(|editor, window, cx| {
 5723        editor.move_down(&Default::default(), window, cx);
 5724    });
 5725    cx.assert_editor_state(
 5726        &"
 5727            zero
 5728            one
 5729            two
 5730            three
 5731            ˇfour
 5732            five
 5733        "
 5734        .unindent(),
 5735    );
 5736}
 5737
 5738#[gpui::test]
 5739fn test_transpose(cx: &mut TestAppContext) {
 5740    init_test(cx, |_| {});
 5741
 5742    _ = cx.add_window(|window, cx| {
 5743        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5744        editor.set_style(EditorStyle::default(), window, cx);
 5745        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5746            s.select_ranges([1..1])
 5747        });
 5748        editor.transpose(&Default::default(), window, cx);
 5749        assert_eq!(editor.text(cx), "bac");
 5750        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5751
 5752        editor.transpose(&Default::default(), window, cx);
 5753        assert_eq!(editor.text(cx), "bca");
 5754        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5755
 5756        editor.transpose(&Default::default(), window, cx);
 5757        assert_eq!(editor.text(cx), "bac");
 5758        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5759
 5760        editor
 5761    });
 5762
 5763    _ = cx.add_window(|window, cx| {
 5764        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5765        editor.set_style(EditorStyle::default(), window, cx);
 5766        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5767            s.select_ranges([3..3])
 5768        });
 5769        editor.transpose(&Default::default(), window, cx);
 5770        assert_eq!(editor.text(cx), "acb\nde");
 5771        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5772
 5773        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5774            s.select_ranges([4..4])
 5775        });
 5776        editor.transpose(&Default::default(), window, cx);
 5777        assert_eq!(editor.text(cx), "acbd\ne");
 5778        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5779
 5780        editor.transpose(&Default::default(), window, cx);
 5781        assert_eq!(editor.text(cx), "acbde\n");
 5782        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5783
 5784        editor.transpose(&Default::default(), window, cx);
 5785        assert_eq!(editor.text(cx), "acbd\ne");
 5786        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5787
 5788        editor
 5789    });
 5790
 5791    _ = cx.add_window(|window, cx| {
 5792        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5793        editor.set_style(EditorStyle::default(), window, cx);
 5794        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5795            s.select_ranges([1..1, 2..2, 4..4])
 5796        });
 5797        editor.transpose(&Default::default(), window, cx);
 5798        assert_eq!(editor.text(cx), "bacd\ne");
 5799        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5800
 5801        editor.transpose(&Default::default(), window, cx);
 5802        assert_eq!(editor.text(cx), "bcade\n");
 5803        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5804
 5805        editor.transpose(&Default::default(), window, cx);
 5806        assert_eq!(editor.text(cx), "bcda\ne");
 5807        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5808
 5809        editor.transpose(&Default::default(), window, cx);
 5810        assert_eq!(editor.text(cx), "bcade\n");
 5811        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5812
 5813        editor.transpose(&Default::default(), window, cx);
 5814        assert_eq!(editor.text(cx), "bcaed\n");
 5815        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5816
 5817        editor
 5818    });
 5819
 5820    _ = cx.add_window(|window, cx| {
 5821        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5822        editor.set_style(EditorStyle::default(), window, cx);
 5823        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5824            s.select_ranges([4..4])
 5825        });
 5826        editor.transpose(&Default::default(), window, cx);
 5827        assert_eq!(editor.text(cx), "🏀🍐✋");
 5828        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5829
 5830        editor.transpose(&Default::default(), window, cx);
 5831        assert_eq!(editor.text(cx), "🏀✋🍐");
 5832        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5833
 5834        editor.transpose(&Default::default(), window, cx);
 5835        assert_eq!(editor.text(cx), "🏀🍐✋");
 5836        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5837
 5838        editor
 5839    });
 5840}
 5841
 5842#[gpui::test]
 5843async fn test_rewrap(cx: &mut TestAppContext) {
 5844    init_test(cx, |settings| {
 5845        settings.languages.0.extend([
 5846            (
 5847                "Markdown".into(),
 5848                LanguageSettingsContent {
 5849                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5850                    preferred_line_length: Some(40),
 5851                    ..Default::default()
 5852                },
 5853            ),
 5854            (
 5855                "Plain Text".into(),
 5856                LanguageSettingsContent {
 5857                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5858                    preferred_line_length: Some(40),
 5859                    ..Default::default()
 5860                },
 5861            ),
 5862            (
 5863                "C++".into(),
 5864                LanguageSettingsContent {
 5865                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5866                    preferred_line_length: Some(40),
 5867                    ..Default::default()
 5868                },
 5869            ),
 5870            (
 5871                "Python".into(),
 5872                LanguageSettingsContent {
 5873                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5874                    preferred_line_length: Some(40),
 5875                    ..Default::default()
 5876                },
 5877            ),
 5878            (
 5879                "Rust".into(),
 5880                LanguageSettingsContent {
 5881                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5882                    preferred_line_length: Some(40),
 5883                    ..Default::default()
 5884                },
 5885            ),
 5886        ])
 5887    });
 5888
 5889    let mut cx = EditorTestContext::new(cx).await;
 5890
 5891    let cpp_language = Arc::new(Language::new(
 5892        LanguageConfig {
 5893            name: "C++".into(),
 5894            line_comments: vec!["// ".into()],
 5895            ..LanguageConfig::default()
 5896        },
 5897        None,
 5898    ));
 5899    let python_language = Arc::new(Language::new(
 5900        LanguageConfig {
 5901            name: "Python".into(),
 5902            line_comments: vec!["# ".into()],
 5903            ..LanguageConfig::default()
 5904        },
 5905        None,
 5906    ));
 5907    let markdown_language = Arc::new(Language::new(
 5908        LanguageConfig {
 5909            name: "Markdown".into(),
 5910            rewrap_prefixes: vec![
 5911                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 5912                regex::Regex::new("[-*+]\\s+").unwrap(),
 5913            ],
 5914            ..LanguageConfig::default()
 5915        },
 5916        None,
 5917    ));
 5918    let rust_language = Arc::new(
 5919        Language::new(
 5920            LanguageConfig {
 5921                name: "Rust".into(),
 5922                line_comments: vec!["// ".into(), "/// ".into()],
 5923                ..LanguageConfig::default()
 5924            },
 5925            Some(tree_sitter_rust::LANGUAGE.into()),
 5926        )
 5927        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 5928        .unwrap(),
 5929    );
 5930
 5931    let plaintext_language = Arc::new(Language::new(
 5932        LanguageConfig {
 5933            name: "Plain Text".into(),
 5934            ..LanguageConfig::default()
 5935        },
 5936        None,
 5937    ));
 5938
 5939    // Test basic rewrapping of a long line with a cursor
 5940    assert_rewrap(
 5941        indoc! {"
 5942            // ˇThis is a long comment that needs to be wrapped.
 5943        "},
 5944        indoc! {"
 5945            // ˇThis is a long comment that needs to
 5946            // be wrapped.
 5947        "},
 5948        cpp_language.clone(),
 5949        &mut cx,
 5950    );
 5951
 5952    // Test rewrapping a full selection
 5953    assert_rewrap(
 5954        indoc! {"
 5955            «// This selected long comment needs to be wrapped.ˇ»"
 5956        },
 5957        indoc! {"
 5958            «// This selected long comment needs to
 5959            // be wrapped.ˇ»"
 5960        },
 5961        cpp_language.clone(),
 5962        &mut cx,
 5963    );
 5964
 5965    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 5966    assert_rewrap(
 5967        indoc! {"
 5968            // ˇThis is the first line.
 5969            // Thisˇ is the second line.
 5970            // This is the thirdˇ line, all part of one paragraph.
 5971         "},
 5972        indoc! {"
 5973            // ˇThis is the first line. Thisˇ is the
 5974            // second line. This is the thirdˇ line,
 5975            // all part of one paragraph.
 5976         "},
 5977        cpp_language.clone(),
 5978        &mut cx,
 5979    );
 5980
 5981    // Test multiple cursors in different paragraphs trigger separate rewraps
 5982    assert_rewrap(
 5983        indoc! {"
 5984            // ˇThis is the first paragraph, first line.
 5985            // ˇThis is the first paragraph, second line.
 5986
 5987            // ˇThis is the second paragraph, first line.
 5988            // ˇThis is the second paragraph, second line.
 5989        "},
 5990        indoc! {"
 5991            // ˇThis is the first paragraph, first
 5992            // line. ˇThis is the first paragraph,
 5993            // second line.
 5994
 5995            // ˇThis is the second paragraph, first
 5996            // line. ˇThis is the second paragraph,
 5997            // second line.
 5998        "},
 5999        cpp_language.clone(),
 6000        &mut cx,
 6001    );
 6002
 6003    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 6004    assert_rewrap(
 6005        indoc! {"
 6006            «// A regular long long comment to be wrapped.
 6007            /// A documentation long comment to be wrapped.ˇ»
 6008          "},
 6009        indoc! {"
 6010            «// A regular long long comment to be
 6011            // wrapped.
 6012            /// A documentation long comment to be
 6013            /// wrapped.ˇ»
 6014          "},
 6015        rust_language.clone(),
 6016        &mut cx,
 6017    );
 6018
 6019    // Test that change in indentation level trigger seperate rewraps
 6020    assert_rewrap(
 6021        indoc! {"
 6022            fn foo() {
 6023                «// This is a long comment at the base indent.
 6024                    // This is a long comment at the next indent.ˇ»
 6025            }
 6026        "},
 6027        indoc! {"
 6028            fn foo() {
 6029                «// This is a long comment at the
 6030                // base indent.
 6031                    // This is a long comment at the
 6032                    // next indent.ˇ»
 6033            }
 6034        "},
 6035        rust_language.clone(),
 6036        &mut cx,
 6037    );
 6038
 6039    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6040    assert_rewrap(
 6041        indoc! {"
 6042            # ˇThis is a long comment using a pound sign.
 6043        "},
 6044        indoc! {"
 6045            # ˇThis is a long comment using a pound
 6046            # sign.
 6047        "},
 6048        python_language,
 6049        &mut cx,
 6050    );
 6051
 6052    // Test rewrapping only affects comments, not code even when selected
 6053    assert_rewrap(
 6054        indoc! {"
 6055            «/// This doc comment is long and should be wrapped.
 6056            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6057        "},
 6058        indoc! {"
 6059            «/// This doc comment is long and should
 6060            /// be wrapped.
 6061            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6062        "},
 6063        rust_language.clone(),
 6064        &mut cx,
 6065    );
 6066
 6067    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6068    assert_rewrap(
 6069        indoc! {"
 6070            # Header
 6071
 6072            A long long long line of markdown text to wrap.ˇ
 6073         "},
 6074        indoc! {"
 6075            # Header
 6076
 6077            A long long long line of markdown text
 6078            to wrap.ˇ
 6079         "},
 6080        markdown_language.clone(),
 6081        &mut cx,
 6082    );
 6083
 6084    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6085    assert_rewrap(
 6086        indoc! {"
 6087            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6088            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6089            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6090        "},
 6091        indoc! {"
 6092            «1. This is a numbered list item that is
 6093               very long and needs to be wrapped
 6094               properly.
 6095            2. This is a numbered list item that is
 6096               very long and needs to be wrapped
 6097               properly.
 6098            - This is an unordered list item that is
 6099              also very long and should not merge
 6100              with the numbered item.ˇ»
 6101        "},
 6102        markdown_language.clone(),
 6103        &mut cx,
 6104    );
 6105
 6106    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6107    assert_rewrap(
 6108        indoc! {"
 6109            «1. This is a numbered list item that is
 6110            very long and needs to be wrapped
 6111            properly.
 6112            2. This is a numbered list item that is
 6113            very long and needs to be wrapped
 6114            properly.
 6115            - This is an unordered list item that is
 6116            also very long and should not merge with
 6117            the numbered item.ˇ»
 6118        "},
 6119        indoc! {"
 6120            «1. This is a numbered list item that is
 6121               very long and needs to be wrapped
 6122               properly.
 6123            2. This is a numbered list item that is
 6124               very long and needs to be wrapped
 6125               properly.
 6126            - This is an unordered list item that is
 6127              also very long and should not merge
 6128              with the numbered item.ˇ»
 6129        "},
 6130        markdown_language.clone(),
 6131        &mut cx,
 6132    );
 6133
 6134    // Test that rewrapping maintain indents even when they already exists.
 6135    assert_rewrap(
 6136        indoc! {"
 6137            «1. This is a numbered list
 6138               item that is very long and needs to be wrapped properly.
 6139            2. This is a numbered list
 6140               item that is very long and needs to be wrapped properly.
 6141            - This is an unordered list item that is also very long and
 6142              should not merge with the numbered item.ˇ»
 6143        "},
 6144        indoc! {"
 6145            «1. This is a numbered list item that is
 6146               very long and needs to be wrapped
 6147               properly.
 6148            2. This is a numbered list item that is
 6149               very long and needs to be wrapped
 6150               properly.
 6151            - This is an unordered list item that is
 6152              also very long and should not merge
 6153              with the numbered item.ˇ»
 6154        "},
 6155        markdown_language,
 6156        &mut cx,
 6157    );
 6158
 6159    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6160    assert_rewrap(
 6161        indoc! {"
 6162            ˇThis is a very long line of plain text that will be wrapped.
 6163        "},
 6164        indoc! {"
 6165            ˇThis is a very long line of plain text
 6166            that will be wrapped.
 6167        "},
 6168        plaintext_language.clone(),
 6169        &mut cx,
 6170    );
 6171
 6172    // Test that non-commented code acts as a paragraph boundary within a selection
 6173    assert_rewrap(
 6174        indoc! {"
 6175               «// This is the first long comment block to be wrapped.
 6176               fn my_func(a: u32);
 6177               // This is the second long comment block to be wrapped.ˇ»
 6178           "},
 6179        indoc! {"
 6180               «// This is the first long comment block
 6181               // to be wrapped.
 6182               fn my_func(a: u32);
 6183               // This is the second long comment block
 6184               // to be wrapped.ˇ»
 6185           "},
 6186        rust_language,
 6187        &mut cx,
 6188    );
 6189
 6190    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6191    assert_rewrap(
 6192        indoc! {"
 6193            «ˇThis is a very long line that will be wrapped.
 6194
 6195            This is another paragraph in the same selection.»
 6196
 6197            «\tThis is a very long indented line that will be wrapped.ˇ»
 6198         "},
 6199        indoc! {"
 6200            «ˇThis is a very long line that will be
 6201            wrapped.
 6202
 6203            This is another paragraph in the same
 6204            selection.»
 6205
 6206            «\tThis is a very long indented line
 6207            \tthat will be wrapped.ˇ»
 6208         "},
 6209        plaintext_language,
 6210        &mut cx,
 6211    );
 6212
 6213    // Test that an empty comment line acts as a paragraph boundary
 6214    assert_rewrap(
 6215        indoc! {"
 6216            // ˇThis is a long comment that will be wrapped.
 6217            //
 6218            // And this is another long comment that will also be wrapped.ˇ
 6219         "},
 6220        indoc! {"
 6221            // ˇThis is a long comment that will be
 6222            // wrapped.
 6223            //
 6224            // And this is another long comment that
 6225            // will also be wrapped.ˇ
 6226         "},
 6227        cpp_language,
 6228        &mut cx,
 6229    );
 6230
 6231    #[track_caller]
 6232    fn assert_rewrap(
 6233        unwrapped_text: &str,
 6234        wrapped_text: &str,
 6235        language: Arc<Language>,
 6236        cx: &mut EditorTestContext,
 6237    ) {
 6238        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6239        cx.set_state(unwrapped_text);
 6240        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6241        cx.assert_editor_state(wrapped_text);
 6242    }
 6243}
 6244
 6245#[gpui::test]
 6246async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6247    init_test(cx, |settings| {
 6248        settings.languages.0.extend([(
 6249            "Rust".into(),
 6250            LanguageSettingsContent {
 6251                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6252                preferred_line_length: Some(40),
 6253                ..Default::default()
 6254            },
 6255        )])
 6256    });
 6257
 6258    let mut cx = EditorTestContext::new(cx).await;
 6259
 6260    let rust_lang = Arc::new(
 6261        Language::new(
 6262            LanguageConfig {
 6263                name: "Rust".into(),
 6264                line_comments: vec!["// ".into()],
 6265                block_comment: Some(BlockCommentConfig {
 6266                    start: "/*".into(),
 6267                    end: "*/".into(),
 6268                    prefix: "* ".into(),
 6269                    tab_size: 1,
 6270                }),
 6271                documentation_comment: Some(BlockCommentConfig {
 6272                    start: "/**".into(),
 6273                    end: "*/".into(),
 6274                    prefix: "* ".into(),
 6275                    tab_size: 1,
 6276                }),
 6277
 6278                ..LanguageConfig::default()
 6279            },
 6280            Some(tree_sitter_rust::LANGUAGE.into()),
 6281        )
 6282        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6283        .unwrap(),
 6284    );
 6285
 6286    // regular block comment
 6287    assert_rewrap(
 6288        indoc! {"
 6289            /*
 6290             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6291             */
 6292            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6293        "},
 6294        indoc! {"
 6295            /*
 6296             *ˇ Lorem ipsum dolor sit amet,
 6297             * consectetur adipiscing elit.
 6298             */
 6299            /*
 6300             *ˇ Lorem ipsum dolor sit amet,
 6301             * consectetur adipiscing elit.
 6302             */
 6303        "},
 6304        rust_lang.clone(),
 6305        &mut cx,
 6306    );
 6307
 6308    // indent is respected
 6309    assert_rewrap(
 6310        indoc! {"
 6311            {}
 6312                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6313        "},
 6314        indoc! {"
 6315            {}
 6316                /*
 6317                 *ˇ Lorem ipsum dolor sit amet,
 6318                 * consectetur adipiscing elit.
 6319                 */
 6320        "},
 6321        rust_lang.clone(),
 6322        &mut cx,
 6323    );
 6324
 6325    // short block comments with inline delimiters
 6326    assert_rewrap(
 6327        indoc! {"
 6328            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6329            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6330             */
 6331            /*
 6332             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6333        "},
 6334        indoc! {"
 6335            /*
 6336             *ˇ Lorem ipsum dolor sit amet,
 6337             * consectetur adipiscing elit.
 6338             */
 6339            /*
 6340             *ˇ Lorem ipsum dolor sit amet,
 6341             * consectetur adipiscing elit.
 6342             */
 6343            /*
 6344             *ˇ Lorem ipsum dolor sit amet,
 6345             * consectetur adipiscing elit.
 6346             */
 6347        "},
 6348        rust_lang.clone(),
 6349        &mut cx,
 6350    );
 6351
 6352    // multiline block comment with inline start/end delimiters
 6353    assert_rewrap(
 6354        indoc! {"
 6355            /*ˇ Lorem ipsum dolor sit amet,
 6356             * consectetur adipiscing elit. */
 6357        "},
 6358        indoc! {"
 6359            /*
 6360             *ˇ Lorem ipsum dolor sit amet,
 6361             * consectetur adipiscing elit.
 6362             */
 6363        "},
 6364        rust_lang.clone(),
 6365        &mut cx,
 6366    );
 6367
 6368    // block comment rewrap still respects paragraph bounds
 6369    assert_rewrap(
 6370        indoc! {"
 6371            /*
 6372             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6373             *
 6374             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6375             */
 6376        "},
 6377        indoc! {"
 6378            /*
 6379             *ˇ Lorem ipsum dolor sit amet,
 6380             * consectetur adipiscing elit.
 6381             *
 6382             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6383             */
 6384        "},
 6385        rust_lang.clone(),
 6386        &mut cx,
 6387    );
 6388
 6389    // documentation comments
 6390    assert_rewrap(
 6391        indoc! {"
 6392            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6393            /**
 6394             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6395             */
 6396        "},
 6397        indoc! {"
 6398            /**
 6399             *ˇ Lorem ipsum dolor sit amet,
 6400             * consectetur adipiscing elit.
 6401             */
 6402            /**
 6403             *ˇ Lorem ipsum dolor sit amet,
 6404             * consectetur adipiscing elit.
 6405             */
 6406        "},
 6407        rust_lang.clone(),
 6408        &mut cx,
 6409    );
 6410
 6411    // different, adjacent comments
 6412    assert_rewrap(
 6413        indoc! {"
 6414            /**
 6415             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6416             */
 6417            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6418            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6419        "},
 6420        indoc! {"
 6421            /**
 6422             *ˇ Lorem ipsum dolor sit amet,
 6423             * consectetur adipiscing elit.
 6424             */
 6425            /*
 6426             *ˇ Lorem ipsum dolor sit amet,
 6427             * consectetur adipiscing elit.
 6428             */
 6429            //ˇ Lorem ipsum dolor sit amet,
 6430            // consectetur adipiscing elit.
 6431        "},
 6432        rust_lang.clone(),
 6433        &mut cx,
 6434    );
 6435
 6436    // selection w/ single short block comment
 6437    assert_rewrap(
 6438        indoc! {"
 6439            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6440        "},
 6441        indoc! {"
 6442            «/*
 6443             * Lorem ipsum dolor sit amet,
 6444             * consectetur adipiscing elit.
 6445             */ˇ»
 6446        "},
 6447        rust_lang.clone(),
 6448        &mut cx,
 6449    );
 6450
 6451    // rewrapping a single comment w/ abutting comments
 6452    assert_rewrap(
 6453        indoc! {"
 6454            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6455            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6456        "},
 6457        indoc! {"
 6458            /*
 6459             * ˇLorem ipsum dolor sit amet,
 6460             * consectetur adipiscing elit.
 6461             */
 6462            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6463        "},
 6464        rust_lang.clone(),
 6465        &mut cx,
 6466    );
 6467
 6468    // selection w/ non-abutting short block comments
 6469    assert_rewrap(
 6470        indoc! {"
 6471            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6472
 6473            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6474        "},
 6475        indoc! {"
 6476            «/*
 6477             * Lorem ipsum dolor sit amet,
 6478             * consectetur adipiscing elit.
 6479             */
 6480
 6481            /*
 6482             * Lorem ipsum dolor sit amet,
 6483             * consectetur adipiscing elit.
 6484             */ˇ»
 6485        "},
 6486        rust_lang.clone(),
 6487        &mut cx,
 6488    );
 6489
 6490    // selection of multiline block comments
 6491    assert_rewrap(
 6492        indoc! {"
 6493            «/* Lorem ipsum dolor sit amet,
 6494             * consectetur adipiscing elit. */ˇ»
 6495        "},
 6496        indoc! {"
 6497            «/*
 6498             * Lorem ipsum dolor sit amet,
 6499             * consectetur adipiscing elit.
 6500             */ˇ»
 6501        "},
 6502        rust_lang.clone(),
 6503        &mut cx,
 6504    );
 6505
 6506    // partial selection of multiline block comments
 6507    assert_rewrap(
 6508        indoc! {"
 6509            «/* Lorem ipsum dolor sit amet,ˇ»
 6510             * consectetur adipiscing elit. */
 6511            /* Lorem ipsum dolor sit amet,
 6512             «* consectetur adipiscing elit. */ˇ»
 6513        "},
 6514        indoc! {"
 6515            «/*
 6516             * Lorem ipsum dolor sit amet,ˇ»
 6517             * consectetur adipiscing elit. */
 6518            /* Lorem ipsum dolor sit amet,
 6519             «* consectetur adipiscing elit.
 6520             */ˇ»
 6521        "},
 6522        rust_lang.clone(),
 6523        &mut cx,
 6524    );
 6525
 6526    // selection w/ abutting short block comments
 6527    // TODO: should not be combined; should rewrap as 2 comments
 6528    assert_rewrap(
 6529        indoc! {"
 6530            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6531            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6532        "},
 6533        // desired behavior:
 6534        // indoc! {"
 6535        //     «/*
 6536        //      * Lorem ipsum dolor sit amet,
 6537        //      * consectetur adipiscing elit.
 6538        //      */
 6539        //     /*
 6540        //      * Lorem ipsum dolor sit amet,
 6541        //      * consectetur adipiscing elit.
 6542        //      */ˇ»
 6543        // "},
 6544        // actual behaviour:
 6545        indoc! {"
 6546            «/*
 6547             * Lorem ipsum dolor sit amet,
 6548             * consectetur adipiscing elit. Lorem
 6549             * ipsum dolor sit amet, consectetur
 6550             * adipiscing elit.
 6551             */ˇ»
 6552        "},
 6553        rust_lang.clone(),
 6554        &mut cx,
 6555    );
 6556
 6557    // TODO: same as above, but with delimiters on separate line
 6558    // assert_rewrap(
 6559    //     indoc! {"
 6560    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6561    //          */
 6562    //         /*
 6563    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6564    //     "},
 6565    //     // desired:
 6566    //     // indoc! {"
 6567    //     //     «/*
 6568    //     //      * Lorem ipsum dolor sit amet,
 6569    //     //      * consectetur adipiscing elit.
 6570    //     //      */
 6571    //     //     /*
 6572    //     //      * Lorem ipsum dolor sit amet,
 6573    //     //      * consectetur adipiscing elit.
 6574    //     //      */ˇ»
 6575    //     // "},
 6576    //     // actual: (but with trailing w/s on the empty lines)
 6577    //     indoc! {"
 6578    //         «/*
 6579    //          * Lorem ipsum dolor sit amet,
 6580    //          * consectetur adipiscing elit.
 6581    //          *
 6582    //          */
 6583    //         /*
 6584    //          *
 6585    //          * Lorem ipsum dolor sit amet,
 6586    //          * consectetur adipiscing elit.
 6587    //          */ˇ»
 6588    //     "},
 6589    //     rust_lang.clone(),
 6590    //     &mut cx,
 6591    // );
 6592
 6593    // TODO these are unhandled edge cases; not correct, just documenting known issues
 6594    assert_rewrap(
 6595        indoc! {"
 6596            /*
 6597             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6598             */
 6599            /*
 6600             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6601            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 6602        "},
 6603        // desired:
 6604        // indoc! {"
 6605        //     /*
 6606        //      *ˇ Lorem ipsum dolor sit amet,
 6607        //      * consectetur adipiscing elit.
 6608        //      */
 6609        //     /*
 6610        //      *ˇ Lorem ipsum dolor sit amet,
 6611        //      * consectetur adipiscing elit.
 6612        //      */
 6613        //     /*
 6614        //      *ˇ Lorem ipsum dolor sit amet
 6615        //      */ /* consectetur adipiscing elit. */
 6616        // "},
 6617        // actual:
 6618        indoc! {"
 6619            /*
 6620             //ˇ Lorem ipsum dolor sit amet,
 6621             // consectetur adipiscing elit.
 6622             */
 6623            /*
 6624             * //ˇ Lorem ipsum dolor sit amet,
 6625             * consectetur adipiscing elit.
 6626             */
 6627            /*
 6628             *ˇ Lorem ipsum dolor sit amet */ /*
 6629             * consectetur adipiscing elit.
 6630             */
 6631        "},
 6632        rust_lang,
 6633        &mut cx,
 6634    );
 6635
 6636    #[track_caller]
 6637    fn assert_rewrap(
 6638        unwrapped_text: &str,
 6639        wrapped_text: &str,
 6640        language: Arc<Language>,
 6641        cx: &mut EditorTestContext,
 6642    ) {
 6643        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6644        cx.set_state(unwrapped_text);
 6645        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6646        cx.assert_editor_state(wrapped_text);
 6647    }
 6648}
 6649
 6650#[gpui::test]
 6651async fn test_hard_wrap(cx: &mut TestAppContext) {
 6652    init_test(cx, |_| {});
 6653    let mut cx = EditorTestContext::new(cx).await;
 6654
 6655    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 6656    cx.update_editor(|editor, _, cx| {
 6657        editor.set_hard_wrap(Some(14), cx);
 6658    });
 6659
 6660    cx.set_state(indoc!(
 6661        "
 6662        one two three ˇ
 6663        "
 6664    ));
 6665    cx.simulate_input("four");
 6666    cx.run_until_parked();
 6667
 6668    cx.assert_editor_state(indoc!(
 6669        "
 6670        one two three
 6671        fourˇ
 6672        "
 6673    ));
 6674
 6675    cx.update_editor(|editor, window, cx| {
 6676        editor.newline(&Default::default(), window, cx);
 6677    });
 6678    cx.run_until_parked();
 6679    cx.assert_editor_state(indoc!(
 6680        "
 6681        one two three
 6682        four
 6683        ˇ
 6684        "
 6685    ));
 6686
 6687    cx.simulate_input("five");
 6688    cx.run_until_parked();
 6689    cx.assert_editor_state(indoc!(
 6690        "
 6691        one two three
 6692        four
 6693        fiveˇ
 6694        "
 6695    ));
 6696
 6697    cx.update_editor(|editor, window, cx| {
 6698        editor.newline(&Default::default(), window, cx);
 6699    });
 6700    cx.run_until_parked();
 6701    cx.simulate_input("# ");
 6702    cx.run_until_parked();
 6703    cx.assert_editor_state(indoc!(
 6704        "
 6705        one two three
 6706        four
 6707        five
 6708        # ˇ
 6709        "
 6710    ));
 6711
 6712    cx.update_editor(|editor, window, cx| {
 6713        editor.newline(&Default::default(), window, cx);
 6714    });
 6715    cx.run_until_parked();
 6716    cx.assert_editor_state(indoc!(
 6717        "
 6718        one two three
 6719        four
 6720        five
 6721        #\x20
 6722 6723        "
 6724    ));
 6725
 6726    cx.simulate_input(" 6");
 6727    cx.run_until_parked();
 6728    cx.assert_editor_state(indoc!(
 6729        "
 6730        one two three
 6731        four
 6732        five
 6733        #
 6734        # 6ˇ
 6735        "
 6736    ));
 6737}
 6738
 6739#[gpui::test]
 6740async fn test_cut_line_ends(cx: &mut TestAppContext) {
 6741    init_test(cx, |_| {});
 6742
 6743    let mut cx = EditorTestContext::new(cx).await;
 6744
 6745    cx.set_state(indoc! {"The quick brownˇ"});
 6746    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 6747    cx.assert_editor_state(indoc! {"The quick brownˇ"});
 6748
 6749    cx.set_state(indoc! {"The emacs foxˇ"});
 6750    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 6751    cx.assert_editor_state(indoc! {"The emacs foxˇ"});
 6752
 6753    cx.set_state(indoc! {"
 6754        The quick« brownˇ»
 6755        fox jumps overˇ
 6756        the lazy dog"});
 6757    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6758    cx.assert_editor_state(indoc! {"
 6759        The quickˇ
 6760        ˇthe lazy dog"});
 6761
 6762    cx.set_state(indoc! {"
 6763        The quick« brownˇ»
 6764        fox jumps overˇ
 6765        the lazy dog"});
 6766    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 6767    cx.assert_editor_state(indoc! {"
 6768        The quickˇ
 6769        fox jumps overˇthe lazy dog"});
 6770
 6771    cx.set_state(indoc! {"
 6772        The quick« brownˇ»
 6773        fox jumps overˇ
 6774        the lazy dog"});
 6775    cx.update_editor(|e, window, cx| {
 6776        e.cut_to_end_of_line(
 6777            &CutToEndOfLine {
 6778                stop_at_newlines: true,
 6779            },
 6780            window,
 6781            cx,
 6782        )
 6783    });
 6784    cx.assert_editor_state(indoc! {"
 6785        The quickˇ
 6786        fox jumps overˇ
 6787        the lazy dog"});
 6788
 6789    cx.set_state(indoc! {"
 6790        The quick« brownˇ»
 6791        fox jumps overˇ
 6792        the lazy dog"});
 6793    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 6794    cx.assert_editor_state(indoc! {"
 6795        The quickˇ
 6796        fox jumps overˇthe lazy dog"});
 6797}
 6798
 6799#[gpui::test]
 6800async fn test_clipboard(cx: &mut TestAppContext) {
 6801    init_test(cx, |_| {});
 6802
 6803    let mut cx = EditorTestContext::new(cx).await;
 6804
 6805    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 6806    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6807    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 6808
 6809    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 6810    cx.set_state("two ˇfour ˇsix ˇ");
 6811    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6812    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 6813
 6814    // Paste again but with only two cursors. Since the number of cursors doesn't
 6815    // match the number of slices in the clipboard, the entire clipboard text
 6816    // is pasted at each cursor.
 6817    cx.set_state("ˇtwo one✅ four three six five ˇ");
 6818    cx.update_editor(|e, window, cx| {
 6819        e.handle_input("( ", window, cx);
 6820        e.paste(&Paste, window, cx);
 6821        e.handle_input(") ", window, cx);
 6822    });
 6823    cx.assert_editor_state(
 6824        &([
 6825            "( one✅ ",
 6826            "three ",
 6827            "five ) ˇtwo one✅ four three six five ( one✅ ",
 6828            "three ",
 6829            "five ) ˇ",
 6830        ]
 6831        .join("\n")),
 6832    );
 6833
 6834    // Cut with three selections, one of which is full-line.
 6835    cx.set_state(indoc! {"
 6836        1«2ˇ»3
 6837        4ˇ567
 6838        «8ˇ»9"});
 6839    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6840    cx.assert_editor_state(indoc! {"
 6841        1ˇ3
 6842        ˇ9"});
 6843
 6844    // Paste with three selections, noticing how the copied selection that was full-line
 6845    // gets inserted before the second cursor.
 6846    cx.set_state(indoc! {"
 6847        1ˇ3
 6848 6849        «oˇ»ne"});
 6850    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6851    cx.assert_editor_state(indoc! {"
 6852        12ˇ3
 6853        4567
 6854 6855        8ˇne"});
 6856
 6857    // Copy with a single cursor only, which writes the whole line into the clipboard.
 6858    cx.set_state(indoc! {"
 6859        The quick brown
 6860        fox juˇmps over
 6861        the lazy dog"});
 6862    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6863    assert_eq!(
 6864        cx.read_from_clipboard()
 6865            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6866        Some("fox jumps over\n".to_string())
 6867    );
 6868
 6869    // Paste with three selections, noticing how the copied full-line selection is inserted
 6870    // before the empty selections but replaces the selection that is non-empty.
 6871    cx.set_state(indoc! {"
 6872        Tˇhe quick brown
 6873        «foˇ»x jumps over
 6874        tˇhe lazy dog"});
 6875    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6876    cx.assert_editor_state(indoc! {"
 6877        fox jumps over
 6878        Tˇhe quick brown
 6879        fox jumps over
 6880        ˇx jumps over
 6881        fox jumps over
 6882        tˇhe lazy dog"});
 6883}
 6884
 6885#[gpui::test]
 6886async fn test_copy_trim(cx: &mut TestAppContext) {
 6887    init_test(cx, |_| {});
 6888
 6889    let mut cx = EditorTestContext::new(cx).await;
 6890    cx.set_state(
 6891        r#"            «for selection in selections.iter() {
 6892            let mut start = selection.start;
 6893            let mut end = selection.end;
 6894            let is_entire_line = selection.is_empty();
 6895            if is_entire_line {
 6896                start = Point::new(start.row, 0);ˇ»
 6897                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6898            }
 6899        "#,
 6900    );
 6901    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6902    assert_eq!(
 6903        cx.read_from_clipboard()
 6904            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6905        Some(
 6906            "for selection in selections.iter() {
 6907            let mut start = selection.start;
 6908            let mut end = selection.end;
 6909            let is_entire_line = selection.is_empty();
 6910            if is_entire_line {
 6911                start = Point::new(start.row, 0);"
 6912                .to_string()
 6913        ),
 6914        "Regular copying preserves all indentation selected",
 6915    );
 6916    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6917    assert_eq!(
 6918        cx.read_from_clipboard()
 6919            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6920        Some(
 6921            "for selection in selections.iter() {
 6922let mut start = selection.start;
 6923let mut end = selection.end;
 6924let is_entire_line = selection.is_empty();
 6925if is_entire_line {
 6926    start = Point::new(start.row, 0);"
 6927                .to_string()
 6928        ),
 6929        "Copying with stripping should strip all leading whitespaces"
 6930    );
 6931
 6932    cx.set_state(
 6933        r#"       «     for selection in selections.iter() {
 6934            let mut start = selection.start;
 6935            let mut end = selection.end;
 6936            let is_entire_line = selection.is_empty();
 6937            if is_entire_line {
 6938                start = Point::new(start.row, 0);ˇ»
 6939                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6940            }
 6941        "#,
 6942    );
 6943    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6944    assert_eq!(
 6945        cx.read_from_clipboard()
 6946            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6947        Some(
 6948            "     for selection in selections.iter() {
 6949            let mut start = selection.start;
 6950            let mut end = selection.end;
 6951            let is_entire_line = selection.is_empty();
 6952            if is_entire_line {
 6953                start = Point::new(start.row, 0);"
 6954                .to_string()
 6955        ),
 6956        "Regular copying preserves all indentation selected",
 6957    );
 6958    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6959    assert_eq!(
 6960        cx.read_from_clipboard()
 6961            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6962        Some(
 6963            "for selection in selections.iter() {
 6964let mut start = selection.start;
 6965let mut end = selection.end;
 6966let is_entire_line = selection.is_empty();
 6967if is_entire_line {
 6968    start = Point::new(start.row, 0);"
 6969                .to_string()
 6970        ),
 6971        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 6972    );
 6973
 6974    cx.set_state(
 6975        r#"       «ˇ     for selection in selections.iter() {
 6976            let mut start = selection.start;
 6977            let mut end = selection.end;
 6978            let is_entire_line = selection.is_empty();
 6979            if is_entire_line {
 6980                start = Point::new(start.row, 0);»
 6981                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6982            }
 6983        "#,
 6984    );
 6985    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6986    assert_eq!(
 6987        cx.read_from_clipboard()
 6988            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6989        Some(
 6990            "     for selection in selections.iter() {
 6991            let mut start = selection.start;
 6992            let mut end = selection.end;
 6993            let is_entire_line = selection.is_empty();
 6994            if is_entire_line {
 6995                start = Point::new(start.row, 0);"
 6996                .to_string()
 6997        ),
 6998        "Regular copying for reverse selection works the same",
 6999    );
 7000    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7001    assert_eq!(
 7002        cx.read_from_clipboard()
 7003            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7004        Some(
 7005            "for selection in selections.iter() {
 7006let mut start = selection.start;
 7007let mut end = selection.end;
 7008let is_entire_line = selection.is_empty();
 7009if is_entire_line {
 7010    start = Point::new(start.row, 0);"
 7011                .to_string()
 7012        ),
 7013        "Copying with stripping for reverse selection works the same"
 7014    );
 7015
 7016    cx.set_state(
 7017        r#"            for selection «in selections.iter() {
 7018            let mut start = selection.start;
 7019            let mut end = selection.end;
 7020            let is_entire_line = selection.is_empty();
 7021            if is_entire_line {
 7022                start = Point::new(start.row, 0);ˇ»
 7023                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7024            }
 7025        "#,
 7026    );
 7027    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7028    assert_eq!(
 7029        cx.read_from_clipboard()
 7030            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7031        Some(
 7032            "in selections.iter() {
 7033            let mut start = selection.start;
 7034            let mut end = selection.end;
 7035            let is_entire_line = selection.is_empty();
 7036            if is_entire_line {
 7037                start = Point::new(start.row, 0);"
 7038                .to_string()
 7039        ),
 7040        "When selecting past the indent, the copying works as usual",
 7041    );
 7042    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7043    assert_eq!(
 7044        cx.read_from_clipboard()
 7045            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7046        Some(
 7047            "in selections.iter() {
 7048            let mut start = selection.start;
 7049            let mut end = selection.end;
 7050            let is_entire_line = selection.is_empty();
 7051            if is_entire_line {
 7052                start = Point::new(start.row, 0);"
 7053                .to_string()
 7054        ),
 7055        "When selecting past the indent, nothing is trimmed"
 7056    );
 7057
 7058    cx.set_state(
 7059        r#"            «for selection in selections.iter() {
 7060            let mut start = selection.start;
 7061
 7062            let mut end = selection.end;
 7063            let is_entire_line = selection.is_empty();
 7064            if is_entire_line {
 7065                start = Point::new(start.row, 0);
 7066ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7067            }
 7068        "#,
 7069    );
 7070    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7071    assert_eq!(
 7072        cx.read_from_clipboard()
 7073            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7074        Some(
 7075            "for selection in selections.iter() {
 7076let mut start = selection.start;
 7077
 7078let mut end = selection.end;
 7079let is_entire_line = selection.is_empty();
 7080if is_entire_line {
 7081    start = Point::new(start.row, 0);
 7082"
 7083            .to_string()
 7084        ),
 7085        "Copying with stripping should ignore empty lines"
 7086    );
 7087}
 7088
 7089#[gpui::test]
 7090async fn test_paste_multiline(cx: &mut TestAppContext) {
 7091    init_test(cx, |_| {});
 7092
 7093    let mut cx = EditorTestContext::new(cx).await;
 7094    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7095
 7096    // Cut an indented block, without the leading whitespace.
 7097    cx.set_state(indoc! {"
 7098        const a: B = (
 7099            c(),
 7100            «d(
 7101                e,
 7102                f
 7103            )ˇ»
 7104        );
 7105    "});
 7106    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7107    cx.assert_editor_state(indoc! {"
 7108        const a: B = (
 7109            c(),
 7110            ˇ
 7111        );
 7112    "});
 7113
 7114    // Paste it at the same position.
 7115    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7116    cx.assert_editor_state(indoc! {"
 7117        const a: B = (
 7118            c(),
 7119            d(
 7120                e,
 7121                f
 7122 7123        );
 7124    "});
 7125
 7126    // Paste it at a line with a lower indent level.
 7127    cx.set_state(indoc! {"
 7128        ˇ
 7129        const a: B = (
 7130            c(),
 7131        );
 7132    "});
 7133    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7134    cx.assert_editor_state(indoc! {"
 7135        d(
 7136            e,
 7137            f
 7138 7139        const a: B = (
 7140            c(),
 7141        );
 7142    "});
 7143
 7144    // Cut an indented block, with the leading whitespace.
 7145    cx.set_state(indoc! {"
 7146        const a: B = (
 7147            c(),
 7148        «    d(
 7149                e,
 7150                f
 7151            )
 7152        ˇ»);
 7153    "});
 7154    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7155    cx.assert_editor_state(indoc! {"
 7156        const a: B = (
 7157            c(),
 7158        ˇ);
 7159    "});
 7160
 7161    // Paste it at the same position.
 7162    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7163    cx.assert_editor_state(indoc! {"
 7164        const a: B = (
 7165            c(),
 7166            d(
 7167                e,
 7168                f
 7169            )
 7170        ˇ);
 7171    "});
 7172
 7173    // Paste it at a line with a higher indent level.
 7174    cx.set_state(indoc! {"
 7175        const a: B = (
 7176            c(),
 7177            d(
 7178                e,
 7179 7180            )
 7181        );
 7182    "});
 7183    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7184    cx.assert_editor_state(indoc! {"
 7185        const a: B = (
 7186            c(),
 7187            d(
 7188                e,
 7189                f    d(
 7190                    e,
 7191                    f
 7192                )
 7193        ˇ
 7194            )
 7195        );
 7196    "});
 7197
 7198    // Copy an indented block, starting mid-line
 7199    cx.set_state(indoc! {"
 7200        const a: B = (
 7201            c(),
 7202            somethin«g(
 7203                e,
 7204                f
 7205            )ˇ»
 7206        );
 7207    "});
 7208    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7209
 7210    // Paste it on a line with a lower indent level
 7211    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7212    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7213    cx.assert_editor_state(indoc! {"
 7214        const a: B = (
 7215            c(),
 7216            something(
 7217                e,
 7218                f
 7219            )
 7220        );
 7221        g(
 7222            e,
 7223            f
 7224"});
 7225}
 7226
 7227#[gpui::test]
 7228async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7229    init_test(cx, |_| {});
 7230
 7231    cx.write_to_clipboard(ClipboardItem::new_string(
 7232        "    d(\n        e\n    );\n".into(),
 7233    ));
 7234
 7235    let mut cx = EditorTestContext::new(cx).await;
 7236    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7237
 7238    cx.set_state(indoc! {"
 7239        fn a() {
 7240            b();
 7241            if c() {
 7242                ˇ
 7243            }
 7244        }
 7245    "});
 7246
 7247    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7248    cx.assert_editor_state(indoc! {"
 7249        fn a() {
 7250            b();
 7251            if c() {
 7252                d(
 7253                    e
 7254                );
 7255        ˇ
 7256            }
 7257        }
 7258    "});
 7259
 7260    cx.set_state(indoc! {"
 7261        fn a() {
 7262            b();
 7263            ˇ
 7264        }
 7265    "});
 7266
 7267    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7268    cx.assert_editor_state(indoc! {"
 7269        fn a() {
 7270            b();
 7271            d(
 7272                e
 7273            );
 7274        ˇ
 7275        }
 7276    "});
 7277}
 7278
 7279#[gpui::test]
 7280fn test_select_all(cx: &mut TestAppContext) {
 7281    init_test(cx, |_| {});
 7282
 7283    let editor = cx.add_window(|window, cx| {
 7284        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7285        build_editor(buffer, window, cx)
 7286    });
 7287    _ = editor.update(cx, |editor, window, cx| {
 7288        editor.select_all(&SelectAll, window, cx);
 7289        assert_eq!(
 7290            editor.selections.display_ranges(cx),
 7291            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7292        );
 7293    });
 7294}
 7295
 7296#[gpui::test]
 7297fn test_select_line(cx: &mut TestAppContext) {
 7298    init_test(cx, |_| {});
 7299
 7300    let editor = cx.add_window(|window, cx| {
 7301        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7302        build_editor(buffer, window, cx)
 7303    });
 7304    _ = editor.update(cx, |editor, window, cx| {
 7305        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7306            s.select_display_ranges([
 7307                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7308                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7309                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7310                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7311            ])
 7312        });
 7313        editor.select_line(&SelectLine, window, cx);
 7314        assert_eq!(
 7315            editor.selections.display_ranges(cx),
 7316            vec![
 7317                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7318                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7319            ]
 7320        );
 7321    });
 7322
 7323    _ = editor.update(cx, |editor, window, cx| {
 7324        editor.select_line(&SelectLine, window, cx);
 7325        assert_eq!(
 7326            editor.selections.display_ranges(cx),
 7327            vec![
 7328                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7329                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7330            ]
 7331        );
 7332    });
 7333
 7334    _ = editor.update(cx, |editor, window, cx| {
 7335        editor.select_line(&SelectLine, window, cx);
 7336        assert_eq!(
 7337            editor.selections.display_ranges(cx),
 7338            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 7339        );
 7340    });
 7341}
 7342
 7343#[gpui::test]
 7344async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7345    init_test(cx, |_| {});
 7346    let mut cx = EditorTestContext::new(cx).await;
 7347
 7348    #[track_caller]
 7349    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7350        cx.set_state(initial_state);
 7351        cx.update_editor(|e, window, cx| {
 7352            e.split_selection_into_lines(&Default::default(), window, cx)
 7353        });
 7354        cx.assert_editor_state(expected_state);
 7355    }
 7356
 7357    // Selection starts and ends at the middle of lines, left-to-right
 7358    test(
 7359        &mut cx,
 7360        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7361        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7362    );
 7363    // Same thing, right-to-left
 7364    test(
 7365        &mut cx,
 7366        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7367        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7368    );
 7369
 7370    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7371    test(
 7372        &mut cx,
 7373        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7374        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7375    );
 7376    // Same thing, right-to-left
 7377    test(
 7378        &mut cx,
 7379        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7380        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7381    );
 7382
 7383    // Whole buffer, left-to-right, last line ends with newline
 7384    test(
 7385        &mut cx,
 7386        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7387        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7388    );
 7389    // Same thing, right-to-left
 7390    test(
 7391        &mut cx,
 7392        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 7393        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7394    );
 7395
 7396    // Starts at the end of a line, ends at the start of another
 7397    test(
 7398        &mut cx,
 7399        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 7400        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 7401    );
 7402}
 7403
 7404#[gpui::test]
 7405async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 7406    init_test(cx, |_| {});
 7407
 7408    let editor = cx.add_window(|window, cx| {
 7409        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 7410        build_editor(buffer, window, cx)
 7411    });
 7412
 7413    // setup
 7414    _ = editor.update(cx, |editor, window, cx| {
 7415        editor.fold_creases(
 7416            vec![
 7417                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7418                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7419                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7420            ],
 7421            true,
 7422            window,
 7423            cx,
 7424        );
 7425        assert_eq!(
 7426            editor.display_text(cx),
 7427            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7428        );
 7429    });
 7430
 7431    _ = editor.update(cx, |editor, window, cx| {
 7432        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7433            s.select_display_ranges([
 7434                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7435                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7436                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7437                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7438            ])
 7439        });
 7440        editor.split_selection_into_lines(&Default::default(), window, cx);
 7441        assert_eq!(
 7442            editor.display_text(cx),
 7443            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7444        );
 7445    });
 7446    EditorTestContext::for_editor(editor, cx)
 7447        .await
 7448        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7449
 7450    _ = editor.update(cx, |editor, window, cx| {
 7451        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7452            s.select_display_ranges([
 7453                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7454            ])
 7455        });
 7456        editor.split_selection_into_lines(&Default::default(), window, cx);
 7457        assert_eq!(
 7458            editor.display_text(cx),
 7459            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7460        );
 7461        assert_eq!(
 7462            editor.selections.display_ranges(cx),
 7463            [
 7464                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7465                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7466                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7467                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7468                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7469                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7470                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7471            ]
 7472        );
 7473    });
 7474    EditorTestContext::for_editor(editor, cx)
 7475        .await
 7476        .assert_editor_state(
 7477            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7478        );
 7479}
 7480
 7481#[gpui::test]
 7482async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7483    init_test(cx, |_| {});
 7484
 7485    let mut cx = EditorTestContext::new(cx).await;
 7486
 7487    cx.set_state(indoc!(
 7488        r#"abc
 7489           defˇghi
 7490
 7491           jk
 7492           nlmo
 7493           "#
 7494    ));
 7495
 7496    cx.update_editor(|editor, window, cx| {
 7497        editor.add_selection_above(&Default::default(), window, cx);
 7498    });
 7499
 7500    cx.assert_editor_state(indoc!(
 7501        r#"abcˇ
 7502           defˇghi
 7503
 7504           jk
 7505           nlmo
 7506           "#
 7507    ));
 7508
 7509    cx.update_editor(|editor, window, cx| {
 7510        editor.add_selection_above(&Default::default(), window, cx);
 7511    });
 7512
 7513    cx.assert_editor_state(indoc!(
 7514        r#"abcˇ
 7515            defˇghi
 7516
 7517            jk
 7518            nlmo
 7519            "#
 7520    ));
 7521
 7522    cx.update_editor(|editor, window, cx| {
 7523        editor.add_selection_below(&Default::default(), window, cx);
 7524    });
 7525
 7526    cx.assert_editor_state(indoc!(
 7527        r#"abc
 7528           defˇghi
 7529
 7530           jk
 7531           nlmo
 7532           "#
 7533    ));
 7534
 7535    cx.update_editor(|editor, window, cx| {
 7536        editor.undo_selection(&Default::default(), window, cx);
 7537    });
 7538
 7539    cx.assert_editor_state(indoc!(
 7540        r#"abcˇ
 7541           defˇghi
 7542
 7543           jk
 7544           nlmo
 7545           "#
 7546    ));
 7547
 7548    cx.update_editor(|editor, window, cx| {
 7549        editor.redo_selection(&Default::default(), window, cx);
 7550    });
 7551
 7552    cx.assert_editor_state(indoc!(
 7553        r#"abc
 7554           defˇghi
 7555
 7556           jk
 7557           nlmo
 7558           "#
 7559    ));
 7560
 7561    cx.update_editor(|editor, window, cx| {
 7562        editor.add_selection_below(&Default::default(), window, cx);
 7563    });
 7564
 7565    cx.assert_editor_state(indoc!(
 7566        r#"abc
 7567           defˇghi
 7568           ˇ
 7569           jk
 7570           nlmo
 7571           "#
 7572    ));
 7573
 7574    cx.update_editor(|editor, window, cx| {
 7575        editor.add_selection_below(&Default::default(), window, cx);
 7576    });
 7577
 7578    cx.assert_editor_state(indoc!(
 7579        r#"abc
 7580           defˇghi
 7581           ˇ
 7582           jkˇ
 7583           nlmo
 7584           "#
 7585    ));
 7586
 7587    cx.update_editor(|editor, window, cx| {
 7588        editor.add_selection_below(&Default::default(), window, cx);
 7589    });
 7590
 7591    cx.assert_editor_state(indoc!(
 7592        r#"abc
 7593           defˇghi
 7594           ˇ
 7595           jkˇ
 7596           nlmˇo
 7597           "#
 7598    ));
 7599
 7600    cx.update_editor(|editor, window, cx| {
 7601        editor.add_selection_below(&Default::default(), window, cx);
 7602    });
 7603
 7604    cx.assert_editor_state(indoc!(
 7605        r#"abc
 7606           defˇghi
 7607           ˇ
 7608           jkˇ
 7609           nlmˇo
 7610           ˇ"#
 7611    ));
 7612
 7613    // change selections
 7614    cx.set_state(indoc!(
 7615        r#"abc
 7616           def«ˇg»hi
 7617
 7618           jk
 7619           nlmo
 7620           "#
 7621    ));
 7622
 7623    cx.update_editor(|editor, window, cx| {
 7624        editor.add_selection_below(&Default::default(), window, cx);
 7625    });
 7626
 7627    cx.assert_editor_state(indoc!(
 7628        r#"abc
 7629           def«ˇg»hi
 7630
 7631           jk
 7632           nlm«ˇo»
 7633           "#
 7634    ));
 7635
 7636    cx.update_editor(|editor, window, cx| {
 7637        editor.add_selection_below(&Default::default(), window, cx);
 7638    });
 7639
 7640    cx.assert_editor_state(indoc!(
 7641        r#"abc
 7642           def«ˇg»hi
 7643
 7644           jk
 7645           nlm«ˇo»
 7646           "#
 7647    ));
 7648
 7649    cx.update_editor(|editor, window, cx| {
 7650        editor.add_selection_above(&Default::default(), window, cx);
 7651    });
 7652
 7653    cx.assert_editor_state(indoc!(
 7654        r#"abc
 7655           def«ˇg»hi
 7656
 7657           jk
 7658           nlmo
 7659           "#
 7660    ));
 7661
 7662    cx.update_editor(|editor, window, cx| {
 7663        editor.add_selection_above(&Default::default(), window, cx);
 7664    });
 7665
 7666    cx.assert_editor_state(indoc!(
 7667        r#"abc
 7668           def«ˇg»hi
 7669
 7670           jk
 7671           nlmo
 7672           "#
 7673    ));
 7674
 7675    // Change selections again
 7676    cx.set_state(indoc!(
 7677        r#"a«bc
 7678           defgˇ»hi
 7679
 7680           jk
 7681           nlmo
 7682           "#
 7683    ));
 7684
 7685    cx.update_editor(|editor, window, cx| {
 7686        editor.add_selection_below(&Default::default(), window, cx);
 7687    });
 7688
 7689    cx.assert_editor_state(indoc!(
 7690        r#"a«bcˇ»
 7691           d«efgˇ»hi
 7692
 7693           j«kˇ»
 7694           nlmo
 7695           "#
 7696    ));
 7697
 7698    cx.update_editor(|editor, window, cx| {
 7699        editor.add_selection_below(&Default::default(), window, cx);
 7700    });
 7701    cx.assert_editor_state(indoc!(
 7702        r#"a«bcˇ»
 7703           d«efgˇ»hi
 7704
 7705           j«kˇ»
 7706           n«lmoˇ»
 7707           "#
 7708    ));
 7709    cx.update_editor(|editor, window, cx| {
 7710        editor.add_selection_above(&Default::default(), window, cx);
 7711    });
 7712
 7713    cx.assert_editor_state(indoc!(
 7714        r#"a«bcˇ»
 7715           d«efgˇ»hi
 7716
 7717           j«kˇ»
 7718           nlmo
 7719           "#
 7720    ));
 7721
 7722    // Change selections again
 7723    cx.set_state(indoc!(
 7724        r#"abc
 7725           d«ˇefghi
 7726
 7727           jk
 7728           nlm»o
 7729           "#
 7730    ));
 7731
 7732    cx.update_editor(|editor, window, cx| {
 7733        editor.add_selection_above(&Default::default(), window, cx);
 7734    });
 7735
 7736    cx.assert_editor_state(indoc!(
 7737        r#"a«ˇbc»
 7738           d«ˇef»ghi
 7739
 7740           j«ˇk»
 7741           n«ˇlm»o
 7742           "#
 7743    ));
 7744
 7745    cx.update_editor(|editor, window, cx| {
 7746        editor.add_selection_below(&Default::default(), window, cx);
 7747    });
 7748
 7749    cx.assert_editor_state(indoc!(
 7750        r#"abc
 7751           d«ˇef»ghi
 7752
 7753           j«ˇk»
 7754           n«ˇlm»o
 7755           "#
 7756    ));
 7757}
 7758
 7759#[gpui::test]
 7760async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 7761    init_test(cx, |_| {});
 7762    let mut cx = EditorTestContext::new(cx).await;
 7763
 7764    cx.set_state(indoc!(
 7765        r#"line onˇe
 7766           liˇne two
 7767           line three
 7768           line four"#
 7769    ));
 7770
 7771    cx.update_editor(|editor, window, cx| {
 7772        editor.add_selection_below(&Default::default(), window, cx);
 7773    });
 7774
 7775    // test multiple cursors expand in the same direction
 7776    cx.assert_editor_state(indoc!(
 7777        r#"line onˇe
 7778           liˇne twˇo
 7779           liˇne three
 7780           line four"#
 7781    ));
 7782
 7783    cx.update_editor(|editor, window, cx| {
 7784        editor.add_selection_below(&Default::default(), window, cx);
 7785    });
 7786
 7787    cx.update_editor(|editor, window, cx| {
 7788        editor.add_selection_below(&Default::default(), window, cx);
 7789    });
 7790
 7791    // test multiple cursors expand below overflow
 7792    cx.assert_editor_state(indoc!(
 7793        r#"line onˇe
 7794           liˇne twˇo
 7795           liˇne thˇree
 7796           liˇne foˇur"#
 7797    ));
 7798
 7799    cx.update_editor(|editor, window, cx| {
 7800        editor.add_selection_above(&Default::default(), window, cx);
 7801    });
 7802
 7803    // test multiple cursors retrieves back correctly
 7804    cx.assert_editor_state(indoc!(
 7805        r#"line onˇe
 7806           liˇne twˇo
 7807           liˇne thˇree
 7808           line four"#
 7809    ));
 7810
 7811    cx.update_editor(|editor, window, cx| {
 7812        editor.add_selection_above(&Default::default(), window, cx);
 7813    });
 7814
 7815    cx.update_editor(|editor, window, cx| {
 7816        editor.add_selection_above(&Default::default(), window, cx);
 7817    });
 7818
 7819    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 7820    cx.assert_editor_state(indoc!(
 7821        r#"liˇne onˇe
 7822           liˇne two
 7823           line three
 7824           line four"#
 7825    ));
 7826
 7827    cx.update_editor(|editor, window, cx| {
 7828        editor.undo_selection(&Default::default(), window, cx);
 7829    });
 7830
 7831    // test undo
 7832    cx.assert_editor_state(indoc!(
 7833        r#"line onˇe
 7834           liˇne twˇo
 7835           line three
 7836           line four"#
 7837    ));
 7838
 7839    cx.update_editor(|editor, window, cx| {
 7840        editor.redo_selection(&Default::default(), window, cx);
 7841    });
 7842
 7843    // test redo
 7844    cx.assert_editor_state(indoc!(
 7845        r#"liˇne onˇe
 7846           liˇne two
 7847           line three
 7848           line four"#
 7849    ));
 7850
 7851    cx.set_state(indoc!(
 7852        r#"abcd
 7853           ef«ghˇ»
 7854           ijkl
 7855           «mˇ»nop"#
 7856    ));
 7857
 7858    cx.update_editor(|editor, window, cx| {
 7859        editor.add_selection_above(&Default::default(), window, cx);
 7860    });
 7861
 7862    // test multiple selections expand in the same direction
 7863    cx.assert_editor_state(indoc!(
 7864        r#"ab«cdˇ»
 7865           ef«ghˇ»
 7866           «iˇ»jkl
 7867           «mˇ»nop"#
 7868    ));
 7869
 7870    cx.update_editor(|editor, window, cx| {
 7871        editor.add_selection_above(&Default::default(), window, cx);
 7872    });
 7873
 7874    // test multiple selection upward overflow
 7875    cx.assert_editor_state(indoc!(
 7876        r#"ab«cdˇ»
 7877           «eˇ»f«ghˇ»
 7878           «iˇ»jkl
 7879           «mˇ»nop"#
 7880    ));
 7881
 7882    cx.update_editor(|editor, window, cx| {
 7883        editor.add_selection_below(&Default::default(), window, cx);
 7884    });
 7885
 7886    // test multiple selection retrieves back correctly
 7887    cx.assert_editor_state(indoc!(
 7888        r#"abcd
 7889           ef«ghˇ»
 7890           «iˇ»jkl
 7891           «mˇ»nop"#
 7892    ));
 7893
 7894    cx.update_editor(|editor, window, cx| {
 7895        editor.add_selection_below(&Default::default(), window, cx);
 7896    });
 7897
 7898    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 7899    cx.assert_editor_state(indoc!(
 7900        r#"abcd
 7901           ef«ghˇ»
 7902           ij«klˇ»
 7903           «mˇ»nop"#
 7904    ));
 7905
 7906    cx.update_editor(|editor, window, cx| {
 7907        editor.undo_selection(&Default::default(), window, cx);
 7908    });
 7909
 7910    // test undo
 7911    cx.assert_editor_state(indoc!(
 7912        r#"abcd
 7913           ef«ghˇ»
 7914           «iˇ»jkl
 7915           «mˇ»nop"#
 7916    ));
 7917
 7918    cx.update_editor(|editor, window, cx| {
 7919        editor.redo_selection(&Default::default(), window, cx);
 7920    });
 7921
 7922    // test redo
 7923    cx.assert_editor_state(indoc!(
 7924        r#"abcd
 7925           ef«ghˇ»
 7926           ij«klˇ»
 7927           «mˇ»nop"#
 7928    ));
 7929}
 7930
 7931#[gpui::test]
 7932async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 7933    init_test(cx, |_| {});
 7934    let mut cx = EditorTestContext::new(cx).await;
 7935
 7936    cx.set_state(indoc!(
 7937        r#"line onˇe
 7938           liˇne two
 7939           line three
 7940           line four"#
 7941    ));
 7942
 7943    cx.update_editor(|editor, window, cx| {
 7944        editor.add_selection_below(&Default::default(), window, cx);
 7945        editor.add_selection_below(&Default::default(), window, cx);
 7946        editor.add_selection_below(&Default::default(), window, cx);
 7947    });
 7948
 7949    // initial state with two multi cursor groups
 7950    cx.assert_editor_state(indoc!(
 7951        r#"line onˇe
 7952           liˇne twˇo
 7953           liˇne thˇree
 7954           liˇne foˇur"#
 7955    ));
 7956
 7957    // add single cursor in middle - simulate opt click
 7958    cx.update_editor(|editor, window, cx| {
 7959        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 7960        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 7961        editor.end_selection(window, cx);
 7962    });
 7963
 7964    cx.assert_editor_state(indoc!(
 7965        r#"line onˇe
 7966           liˇne twˇo
 7967           liˇneˇ thˇree
 7968           liˇne foˇur"#
 7969    ));
 7970
 7971    cx.update_editor(|editor, window, cx| {
 7972        editor.add_selection_above(&Default::default(), window, cx);
 7973    });
 7974
 7975    // test new added selection expands above and existing selection shrinks
 7976    cx.assert_editor_state(indoc!(
 7977        r#"line onˇe
 7978           liˇneˇ twˇo
 7979           liˇneˇ thˇree
 7980           line four"#
 7981    ));
 7982
 7983    cx.update_editor(|editor, window, cx| {
 7984        editor.add_selection_above(&Default::default(), window, cx);
 7985    });
 7986
 7987    // test new added selection expands above and existing selection shrinks
 7988    cx.assert_editor_state(indoc!(
 7989        r#"lineˇ onˇe
 7990           liˇneˇ twˇo
 7991           lineˇ three
 7992           line four"#
 7993    ));
 7994
 7995    // intial state with two selection groups
 7996    cx.set_state(indoc!(
 7997        r#"abcd
 7998           ef«ghˇ»
 7999           ijkl
 8000           «mˇ»nop"#
 8001    ));
 8002
 8003    cx.update_editor(|editor, window, cx| {
 8004        editor.add_selection_above(&Default::default(), window, cx);
 8005        editor.add_selection_above(&Default::default(), window, cx);
 8006    });
 8007
 8008    cx.assert_editor_state(indoc!(
 8009        r#"ab«cdˇ»
 8010           «eˇ»f«ghˇ»
 8011           «iˇ»jkl
 8012           «mˇ»nop"#
 8013    ));
 8014
 8015    // add single selection in middle - simulate opt drag
 8016    cx.update_editor(|editor, window, cx| {
 8017        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8018        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8019        editor.update_selection(
 8020            DisplayPoint::new(DisplayRow(2), 4),
 8021            0,
 8022            gpui::Point::<f32>::default(),
 8023            window,
 8024            cx,
 8025        );
 8026        editor.end_selection(window, cx);
 8027    });
 8028
 8029    cx.assert_editor_state(indoc!(
 8030        r#"ab«cdˇ»
 8031           «eˇ»f«ghˇ»
 8032           «iˇ»jk«lˇ»
 8033           «mˇ»nop"#
 8034    ));
 8035
 8036    cx.update_editor(|editor, window, cx| {
 8037        editor.add_selection_below(&Default::default(), window, cx);
 8038    });
 8039
 8040    // test new added selection expands below, others shrinks from above
 8041    cx.assert_editor_state(indoc!(
 8042        r#"abcd
 8043           ef«ghˇ»
 8044           «iˇ»jk«lˇ»
 8045           «mˇ»no«pˇ»"#
 8046    ));
 8047}
 8048
 8049#[gpui::test]
 8050async fn test_select_next(cx: &mut TestAppContext) {
 8051    init_test(cx, |_| {});
 8052
 8053    let mut cx = EditorTestContext::new(cx).await;
 8054    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8055
 8056    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8057        .unwrap();
 8058    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8059
 8060    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8061        .unwrap();
 8062    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8063
 8064    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8065    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8066
 8067    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8068    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8069
 8070    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8071        .unwrap();
 8072    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8073
 8074    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8075        .unwrap();
 8076    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8077
 8078    // Test selection direction should be preserved
 8079    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8080
 8081    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8082        .unwrap();
 8083    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8084}
 8085
 8086#[gpui::test]
 8087async fn test_select_all_matches(cx: &mut TestAppContext) {
 8088    init_test(cx, |_| {});
 8089
 8090    let mut cx = EditorTestContext::new(cx).await;
 8091
 8092    // Test caret-only selections
 8093    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8094    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8095        .unwrap();
 8096    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8097
 8098    // Test left-to-right selections
 8099    cx.set_state("abc\n«abcˇ»\nabc");
 8100    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8101        .unwrap();
 8102    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8103
 8104    // Test right-to-left selections
 8105    cx.set_state("abc\n«ˇabc»\nabc");
 8106    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8107        .unwrap();
 8108    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8109
 8110    // Test selecting whitespace with caret selection
 8111    cx.set_state("abc\nˇ   abc\nabc");
 8112    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8113        .unwrap();
 8114    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8115
 8116    // Test selecting whitespace with left-to-right selection
 8117    cx.set_state("abc\n«ˇ  »abc\nabc");
 8118    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8119        .unwrap();
 8120    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8121
 8122    // Test no matches with right-to-left selection
 8123    cx.set_state("abc\n«  ˇ»abc\nabc");
 8124    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8125        .unwrap();
 8126    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8127
 8128    // Test with a single word and clip_at_line_ends=true (#29823)
 8129    cx.set_state("aˇbc");
 8130    cx.update_editor(|e, window, cx| {
 8131        e.set_clip_at_line_ends(true, cx);
 8132        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8133        e.set_clip_at_line_ends(false, cx);
 8134    });
 8135    cx.assert_editor_state("«abcˇ»");
 8136}
 8137
 8138#[gpui::test]
 8139async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8140    init_test(cx, |_| {});
 8141
 8142    let mut cx = EditorTestContext::new(cx).await;
 8143
 8144    let large_body_1 = "\nd".repeat(200);
 8145    let large_body_2 = "\ne".repeat(200);
 8146
 8147    cx.set_state(&format!(
 8148        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8149    ));
 8150    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8151        let scroll_position = editor.scroll_position(cx);
 8152        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8153        scroll_position
 8154    });
 8155
 8156    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8157        .unwrap();
 8158    cx.assert_editor_state(&format!(
 8159        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8160    ));
 8161    let scroll_position_after_selection =
 8162        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8163    assert_eq!(
 8164        initial_scroll_position, scroll_position_after_selection,
 8165        "Scroll position should not change after selecting all matches"
 8166    );
 8167}
 8168
 8169#[gpui::test]
 8170async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8171    init_test(cx, |_| {});
 8172
 8173    let mut cx = EditorLspTestContext::new_rust(
 8174        lsp::ServerCapabilities {
 8175            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8176            ..Default::default()
 8177        },
 8178        cx,
 8179    )
 8180    .await;
 8181
 8182    cx.set_state(indoc! {"
 8183        line 1
 8184        line 2
 8185        linˇe 3
 8186        line 4
 8187        line 5
 8188    "});
 8189
 8190    // Make an edit
 8191    cx.update_editor(|editor, window, cx| {
 8192        editor.handle_input("X", window, cx);
 8193    });
 8194
 8195    // Move cursor to a different position
 8196    cx.update_editor(|editor, window, cx| {
 8197        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8198            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8199        });
 8200    });
 8201
 8202    cx.assert_editor_state(indoc! {"
 8203        line 1
 8204        line 2
 8205        linXe 3
 8206        line 4
 8207        liˇne 5
 8208    "});
 8209
 8210    cx.lsp
 8211        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8212            Ok(Some(vec![lsp::TextEdit::new(
 8213                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8214                "PREFIX ".to_string(),
 8215            )]))
 8216        });
 8217
 8218    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8219        .unwrap()
 8220        .await
 8221        .unwrap();
 8222
 8223    cx.assert_editor_state(indoc! {"
 8224        PREFIX line 1
 8225        line 2
 8226        linXe 3
 8227        line 4
 8228        liˇne 5
 8229    "});
 8230
 8231    // Undo formatting
 8232    cx.update_editor(|editor, window, cx| {
 8233        editor.undo(&Default::default(), window, cx);
 8234    });
 8235
 8236    // Verify cursor moved back to position after edit
 8237    cx.assert_editor_state(indoc! {"
 8238        line 1
 8239        line 2
 8240        linXˇe 3
 8241        line 4
 8242        line 5
 8243    "});
 8244}
 8245
 8246#[gpui::test]
 8247async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8248    init_test(cx, |_| {});
 8249
 8250    let mut cx = EditorTestContext::new(cx).await;
 8251
 8252    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 8253    cx.update_editor(|editor, window, cx| {
 8254        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8255    });
 8256
 8257    cx.set_state(indoc! {"
 8258        line 1
 8259        line 2
 8260        linˇe 3
 8261        line 4
 8262        line 5
 8263        line 6
 8264        line 7
 8265        line 8
 8266        line 9
 8267        line 10
 8268    "});
 8269
 8270    let snapshot = cx.buffer_snapshot();
 8271    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8272
 8273    cx.update(|_, cx| {
 8274        provider.update(cx, |provider, _| {
 8275            provider.set_edit_prediction(Some(edit_prediction::EditPrediction::Local {
 8276                id: None,
 8277                edits: vec![(edit_position..edit_position, "X".into())],
 8278                edit_preview: None,
 8279            }))
 8280        })
 8281    });
 8282
 8283    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8284    cx.update_editor(|editor, window, cx| {
 8285        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8286    });
 8287
 8288    cx.assert_editor_state(indoc! {"
 8289        line 1
 8290        line 2
 8291        lineXˇ 3
 8292        line 4
 8293        line 5
 8294        line 6
 8295        line 7
 8296        line 8
 8297        line 9
 8298        line 10
 8299    "});
 8300
 8301    cx.update_editor(|editor, window, cx| {
 8302        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8303            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8304        });
 8305    });
 8306
 8307    cx.assert_editor_state(indoc! {"
 8308        line 1
 8309        line 2
 8310        lineX 3
 8311        line 4
 8312        line 5
 8313        line 6
 8314        line 7
 8315        line 8
 8316        line 9
 8317        liˇne 10
 8318    "});
 8319
 8320    cx.update_editor(|editor, window, cx| {
 8321        editor.undo(&Default::default(), window, cx);
 8322    });
 8323
 8324    cx.assert_editor_state(indoc! {"
 8325        line 1
 8326        line 2
 8327        lineˇ 3
 8328        line 4
 8329        line 5
 8330        line 6
 8331        line 7
 8332        line 8
 8333        line 9
 8334        line 10
 8335    "});
 8336}
 8337
 8338#[gpui::test]
 8339async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8340    init_test(cx, |_| {});
 8341
 8342    let mut cx = EditorTestContext::new(cx).await;
 8343    cx.set_state(
 8344        r#"let foo = 2;
 8345lˇet foo = 2;
 8346let fooˇ = 2;
 8347let foo = 2;
 8348let foo = ˇ2;"#,
 8349    );
 8350
 8351    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8352        .unwrap();
 8353    cx.assert_editor_state(
 8354        r#"let foo = 2;
 8355«letˇ» foo = 2;
 8356let «fooˇ» = 2;
 8357let foo = 2;
 8358let foo = «2ˇ»;"#,
 8359    );
 8360
 8361    // noop for multiple selections with different contents
 8362    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8363        .unwrap();
 8364    cx.assert_editor_state(
 8365        r#"let foo = 2;
 8366«letˇ» foo = 2;
 8367let «fooˇ» = 2;
 8368let foo = 2;
 8369let foo = «2ˇ»;"#,
 8370    );
 8371
 8372    // Test last selection direction should be preserved
 8373    cx.set_state(
 8374        r#"let foo = 2;
 8375let foo = 2;
 8376let «fooˇ» = 2;
 8377let «ˇfoo» = 2;
 8378let foo = 2;"#,
 8379    );
 8380
 8381    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8382        .unwrap();
 8383    cx.assert_editor_state(
 8384        r#"let foo = 2;
 8385let foo = 2;
 8386let «fooˇ» = 2;
 8387let «ˇfoo» = 2;
 8388let «ˇfoo» = 2;"#,
 8389    );
 8390}
 8391
 8392#[gpui::test]
 8393async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8394    init_test(cx, |_| {});
 8395
 8396    let mut cx =
 8397        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8398
 8399    cx.assert_editor_state(indoc! {"
 8400        ˇbbb
 8401        ccc
 8402
 8403        bbb
 8404        ccc
 8405        "});
 8406    cx.dispatch_action(SelectPrevious::default());
 8407    cx.assert_editor_state(indoc! {"
 8408                «bbbˇ»
 8409                ccc
 8410
 8411                bbb
 8412                ccc
 8413                "});
 8414    cx.dispatch_action(SelectPrevious::default());
 8415    cx.assert_editor_state(indoc! {"
 8416                «bbbˇ»
 8417                ccc
 8418
 8419                «bbbˇ»
 8420                ccc
 8421                "});
 8422}
 8423
 8424#[gpui::test]
 8425async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8426    init_test(cx, |_| {});
 8427
 8428    let mut cx = EditorTestContext::new(cx).await;
 8429    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8430
 8431    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8432        .unwrap();
 8433    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8434
 8435    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8436        .unwrap();
 8437    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8438
 8439    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8440    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8441
 8442    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8443    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8444
 8445    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8446        .unwrap();
 8447    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 8448
 8449    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8450        .unwrap();
 8451    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8452}
 8453
 8454#[gpui::test]
 8455async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8456    init_test(cx, |_| {});
 8457
 8458    let mut cx = EditorTestContext::new(cx).await;
 8459    cx.set_state("");
 8460
 8461    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8462        .unwrap();
 8463    cx.assert_editor_state("«aˇ»");
 8464    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8465        .unwrap();
 8466    cx.assert_editor_state("«aˇ»");
 8467}
 8468
 8469#[gpui::test]
 8470async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8471    init_test(cx, |_| {});
 8472
 8473    let mut cx = EditorTestContext::new(cx).await;
 8474    cx.set_state(
 8475        r#"let foo = 2;
 8476lˇet foo = 2;
 8477let fooˇ = 2;
 8478let foo = 2;
 8479let foo = ˇ2;"#,
 8480    );
 8481
 8482    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8483        .unwrap();
 8484    cx.assert_editor_state(
 8485        r#"let foo = 2;
 8486«letˇ» foo = 2;
 8487let «fooˇ» = 2;
 8488let foo = 2;
 8489let foo = «2ˇ»;"#,
 8490    );
 8491
 8492    // noop for multiple selections with different contents
 8493    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8494        .unwrap();
 8495    cx.assert_editor_state(
 8496        r#"let foo = 2;
 8497«letˇ» foo = 2;
 8498let «fooˇ» = 2;
 8499let foo = 2;
 8500let foo = «2ˇ»;"#,
 8501    );
 8502}
 8503
 8504#[gpui::test]
 8505async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 8506    init_test(cx, |_| {});
 8507
 8508    let mut cx = EditorTestContext::new(cx).await;
 8509    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8510
 8511    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8512        .unwrap();
 8513    // selection direction is preserved
 8514    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8515
 8516    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8517        .unwrap();
 8518    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8519
 8520    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8521    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8522
 8523    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8524    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8525
 8526    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8527        .unwrap();
 8528    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 8529
 8530    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8531        .unwrap();
 8532    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 8533}
 8534
 8535#[gpui::test]
 8536async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 8537    init_test(cx, |_| {});
 8538
 8539    let language = Arc::new(Language::new(
 8540        LanguageConfig::default(),
 8541        Some(tree_sitter_rust::LANGUAGE.into()),
 8542    ));
 8543
 8544    let text = r#"
 8545        use mod1::mod2::{mod3, mod4};
 8546
 8547        fn fn_1(param1: bool, param2: &str) {
 8548            let var1 = "text";
 8549        }
 8550    "#
 8551    .unindent();
 8552
 8553    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8554    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8555    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8556
 8557    editor
 8558        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8559        .await;
 8560
 8561    editor.update_in(cx, |editor, window, cx| {
 8562        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8563            s.select_display_ranges([
 8564                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 8565                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 8566                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 8567            ]);
 8568        });
 8569        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8570    });
 8571    editor.update(cx, |editor, cx| {
 8572        assert_text_with_selections(
 8573            editor,
 8574            indoc! {r#"
 8575                use mod1::mod2::{mod3, «mod4ˇ»};
 8576
 8577                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8578                    let var1 = "«ˇtext»";
 8579                }
 8580            "#},
 8581            cx,
 8582        );
 8583    });
 8584
 8585    editor.update_in(cx, |editor, window, cx| {
 8586        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8587    });
 8588    editor.update(cx, |editor, cx| {
 8589        assert_text_with_selections(
 8590            editor,
 8591            indoc! {r#"
 8592                use mod1::mod2::«{mod3, mod4}ˇ»;
 8593
 8594                «ˇfn fn_1(param1: bool, param2: &str) {
 8595                    let var1 = "text";
 8596 8597            "#},
 8598            cx,
 8599        );
 8600    });
 8601
 8602    editor.update_in(cx, |editor, window, cx| {
 8603        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8604    });
 8605    assert_eq!(
 8606        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8607        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8608    );
 8609
 8610    // Trying to expand the selected syntax node one more time has no effect.
 8611    editor.update_in(cx, |editor, window, cx| {
 8612        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8613    });
 8614    assert_eq!(
 8615        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8616        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8617    );
 8618
 8619    editor.update_in(cx, |editor, window, cx| {
 8620        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8621    });
 8622    editor.update(cx, |editor, cx| {
 8623        assert_text_with_selections(
 8624            editor,
 8625            indoc! {r#"
 8626                use mod1::mod2::«{mod3, mod4}ˇ»;
 8627
 8628                «ˇfn fn_1(param1: bool, param2: &str) {
 8629                    let var1 = "text";
 8630 8631            "#},
 8632            cx,
 8633        );
 8634    });
 8635
 8636    editor.update_in(cx, |editor, window, cx| {
 8637        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8638    });
 8639    editor.update(cx, |editor, cx| {
 8640        assert_text_with_selections(
 8641            editor,
 8642            indoc! {r#"
 8643                use mod1::mod2::{mod3, «mod4ˇ»};
 8644
 8645                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8646                    let var1 = "«ˇtext»";
 8647                }
 8648            "#},
 8649            cx,
 8650        );
 8651    });
 8652
 8653    editor.update_in(cx, |editor, window, cx| {
 8654        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8655    });
 8656    editor.update(cx, |editor, cx| {
 8657        assert_text_with_selections(
 8658            editor,
 8659            indoc! {r#"
 8660                use mod1::mod2::{mod3, moˇd4};
 8661
 8662                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8663                    let var1 = "teˇxt";
 8664                }
 8665            "#},
 8666            cx,
 8667        );
 8668    });
 8669
 8670    // Trying to shrink the selected syntax node one more time has no effect.
 8671    editor.update_in(cx, |editor, window, cx| {
 8672        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8673    });
 8674    editor.update_in(cx, |editor, _, cx| {
 8675        assert_text_with_selections(
 8676            editor,
 8677            indoc! {r#"
 8678                use mod1::mod2::{mod3, moˇd4};
 8679
 8680                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8681                    let var1 = "teˇxt";
 8682                }
 8683            "#},
 8684            cx,
 8685        );
 8686    });
 8687
 8688    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 8689    // a fold.
 8690    editor.update_in(cx, |editor, window, cx| {
 8691        editor.fold_creases(
 8692            vec![
 8693                Crease::simple(
 8694                    Point::new(0, 21)..Point::new(0, 24),
 8695                    FoldPlaceholder::test(),
 8696                ),
 8697                Crease::simple(
 8698                    Point::new(3, 20)..Point::new(3, 22),
 8699                    FoldPlaceholder::test(),
 8700                ),
 8701            ],
 8702            true,
 8703            window,
 8704            cx,
 8705        );
 8706        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8707    });
 8708    editor.update(cx, |editor, cx| {
 8709        assert_text_with_selections(
 8710            editor,
 8711            indoc! {r#"
 8712                use mod1::mod2::«{mod3, mod4}ˇ»;
 8713
 8714                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8715                    let var1 = "«ˇtext»";
 8716                }
 8717            "#},
 8718            cx,
 8719        );
 8720    });
 8721}
 8722
 8723#[gpui::test]
 8724async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 8725    init_test(cx, |_| {});
 8726
 8727    let language = Arc::new(Language::new(
 8728        LanguageConfig::default(),
 8729        Some(tree_sitter_rust::LANGUAGE.into()),
 8730    ));
 8731
 8732    let text = "let a = 2;";
 8733
 8734    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8735    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8736    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8737
 8738    editor
 8739        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8740        .await;
 8741
 8742    // Test case 1: Cursor at end of word
 8743    editor.update_in(cx, |editor, window, cx| {
 8744        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8745            s.select_display_ranges([
 8746                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 8747            ]);
 8748        });
 8749    });
 8750    editor.update(cx, |editor, cx| {
 8751        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 8752    });
 8753    editor.update_in(cx, |editor, window, cx| {
 8754        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8755    });
 8756    editor.update(cx, |editor, cx| {
 8757        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 8758    });
 8759    editor.update_in(cx, |editor, window, cx| {
 8760        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8761    });
 8762    editor.update(cx, |editor, cx| {
 8763        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 8764    });
 8765
 8766    // Test case 2: Cursor at end of statement
 8767    editor.update_in(cx, |editor, window, cx| {
 8768        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8769            s.select_display_ranges([
 8770                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 8771            ]);
 8772        });
 8773    });
 8774    editor.update(cx, |editor, cx| {
 8775        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 8776    });
 8777    editor.update_in(cx, |editor, window, cx| {
 8778        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8779    });
 8780    editor.update(cx, |editor, cx| {
 8781        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 8782    });
 8783}
 8784
 8785#[gpui::test]
 8786async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 8787    init_test(cx, |_| {});
 8788
 8789    let language = Arc::new(Language::new(
 8790        LanguageConfig {
 8791            name: "JavaScript".into(),
 8792            ..Default::default()
 8793        },
 8794        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8795    ));
 8796
 8797    let text = r#"
 8798        let a = {
 8799            key: "value",
 8800        };
 8801    "#
 8802    .unindent();
 8803
 8804    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8805    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8806    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8807
 8808    editor
 8809        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8810        .await;
 8811
 8812    // Test case 1: Cursor after '{'
 8813    editor.update_in(cx, |editor, window, cx| {
 8814        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8815            s.select_display_ranges([
 8816                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 8817            ]);
 8818        });
 8819    });
 8820    editor.update(cx, |editor, cx| {
 8821        assert_text_with_selections(
 8822            editor,
 8823            indoc! {r#"
 8824                let a = {ˇ
 8825                    key: "value",
 8826                };
 8827            "#},
 8828            cx,
 8829        );
 8830    });
 8831    editor.update_in(cx, |editor, window, cx| {
 8832        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8833    });
 8834    editor.update(cx, |editor, cx| {
 8835        assert_text_with_selections(
 8836            editor,
 8837            indoc! {r#"
 8838                let a = «ˇ{
 8839                    key: "value",
 8840                }»;
 8841            "#},
 8842            cx,
 8843        );
 8844    });
 8845
 8846    // Test case 2: Cursor after ':'
 8847    editor.update_in(cx, |editor, window, cx| {
 8848        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8849            s.select_display_ranges([
 8850                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 8851            ]);
 8852        });
 8853    });
 8854    editor.update(cx, |editor, cx| {
 8855        assert_text_with_selections(
 8856            editor,
 8857            indoc! {r#"
 8858                let a = {
 8859                    key:ˇ "value",
 8860                };
 8861            "#},
 8862            cx,
 8863        );
 8864    });
 8865    editor.update_in(cx, |editor, window, cx| {
 8866        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8867    });
 8868    editor.update(cx, |editor, cx| {
 8869        assert_text_with_selections(
 8870            editor,
 8871            indoc! {r#"
 8872                let a = {
 8873                    «ˇkey: "value"»,
 8874                };
 8875            "#},
 8876            cx,
 8877        );
 8878    });
 8879    editor.update_in(cx, |editor, window, cx| {
 8880        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8881    });
 8882    editor.update(cx, |editor, cx| {
 8883        assert_text_with_selections(
 8884            editor,
 8885            indoc! {r#"
 8886                let a = «ˇ{
 8887                    key: "value",
 8888                }»;
 8889            "#},
 8890            cx,
 8891        );
 8892    });
 8893
 8894    // Test case 3: Cursor after ','
 8895    editor.update_in(cx, |editor, window, cx| {
 8896        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8897            s.select_display_ranges([
 8898                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 8899            ]);
 8900        });
 8901    });
 8902    editor.update(cx, |editor, cx| {
 8903        assert_text_with_selections(
 8904            editor,
 8905            indoc! {r#"
 8906                let a = {
 8907                    key: "value",ˇ
 8908                };
 8909            "#},
 8910            cx,
 8911        );
 8912    });
 8913    editor.update_in(cx, |editor, window, cx| {
 8914        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8915    });
 8916    editor.update(cx, |editor, cx| {
 8917        assert_text_with_selections(
 8918            editor,
 8919            indoc! {r#"
 8920                let a = «ˇ{
 8921                    key: "value",
 8922                }»;
 8923            "#},
 8924            cx,
 8925        );
 8926    });
 8927
 8928    // Test case 4: Cursor after ';'
 8929    editor.update_in(cx, |editor, window, cx| {
 8930        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8931            s.select_display_ranges([
 8932                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 8933            ]);
 8934        });
 8935    });
 8936    editor.update(cx, |editor, cx| {
 8937        assert_text_with_selections(
 8938            editor,
 8939            indoc! {r#"
 8940                let a = {
 8941                    key: "value",
 8942                };ˇ
 8943            "#},
 8944            cx,
 8945        );
 8946    });
 8947    editor.update_in(cx, |editor, window, cx| {
 8948        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8949    });
 8950    editor.update(cx, |editor, cx| {
 8951        assert_text_with_selections(
 8952            editor,
 8953            indoc! {r#"
 8954                «ˇlet a = {
 8955                    key: "value",
 8956                };
 8957                »"#},
 8958            cx,
 8959        );
 8960    });
 8961}
 8962
 8963#[gpui::test]
 8964async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 8965    init_test(cx, |_| {});
 8966
 8967    let language = Arc::new(Language::new(
 8968        LanguageConfig::default(),
 8969        Some(tree_sitter_rust::LANGUAGE.into()),
 8970    ));
 8971
 8972    let text = r#"
 8973        use mod1::mod2::{mod3, mod4};
 8974
 8975        fn fn_1(param1: bool, param2: &str) {
 8976            let var1 = "hello world";
 8977        }
 8978    "#
 8979    .unindent();
 8980
 8981    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8982    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8983    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8984
 8985    editor
 8986        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8987        .await;
 8988
 8989    // Test 1: Cursor on a letter of a string word
 8990    editor.update_in(cx, |editor, window, cx| {
 8991        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8992            s.select_display_ranges([
 8993                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 8994            ]);
 8995        });
 8996    });
 8997    editor.update_in(cx, |editor, window, cx| {
 8998        assert_text_with_selections(
 8999            editor,
 9000            indoc! {r#"
 9001                use mod1::mod2::{mod3, mod4};
 9002
 9003                fn fn_1(param1: bool, param2: &str) {
 9004                    let var1 = "hˇello world";
 9005                }
 9006            "#},
 9007            cx,
 9008        );
 9009        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9010        assert_text_with_selections(
 9011            editor,
 9012            indoc! {r#"
 9013                use mod1::mod2::{mod3, mod4};
 9014
 9015                fn fn_1(param1: bool, param2: &str) {
 9016                    let var1 = "«ˇhello» world";
 9017                }
 9018            "#},
 9019            cx,
 9020        );
 9021    });
 9022
 9023    // Test 2: Partial selection within a word
 9024    editor.update_in(cx, |editor, window, cx| {
 9025        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9026            s.select_display_ranges([
 9027                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9028            ]);
 9029        });
 9030    });
 9031    editor.update_in(cx, |editor, window, cx| {
 9032        assert_text_with_selections(
 9033            editor,
 9034            indoc! {r#"
 9035                use mod1::mod2::{mod3, mod4};
 9036
 9037                fn fn_1(param1: bool, param2: &str) {
 9038                    let var1 = "h«elˇ»lo world";
 9039                }
 9040            "#},
 9041            cx,
 9042        );
 9043        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9044        assert_text_with_selections(
 9045            editor,
 9046            indoc! {r#"
 9047                use mod1::mod2::{mod3, mod4};
 9048
 9049                fn fn_1(param1: bool, param2: &str) {
 9050                    let var1 = "«ˇhello» world";
 9051                }
 9052            "#},
 9053            cx,
 9054        );
 9055    });
 9056
 9057    // Test 3: Complete word already selected
 9058    editor.update_in(cx, |editor, window, cx| {
 9059        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9060            s.select_display_ranges([
 9061                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9062            ]);
 9063        });
 9064    });
 9065    editor.update_in(cx, |editor, window, cx| {
 9066        assert_text_with_selections(
 9067            editor,
 9068            indoc! {r#"
 9069                use mod1::mod2::{mod3, mod4};
 9070
 9071                fn fn_1(param1: bool, param2: &str) {
 9072                    let var1 = "«helloˇ» world";
 9073                }
 9074            "#},
 9075            cx,
 9076        );
 9077        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9078        assert_text_with_selections(
 9079            editor,
 9080            indoc! {r#"
 9081                use mod1::mod2::{mod3, mod4};
 9082
 9083                fn fn_1(param1: bool, param2: &str) {
 9084                    let var1 = "«hello worldˇ»";
 9085                }
 9086            "#},
 9087            cx,
 9088        );
 9089    });
 9090
 9091    // Test 4: Selection spanning across words
 9092    editor.update_in(cx, |editor, window, cx| {
 9093        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9094            s.select_display_ranges([
 9095                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9096            ]);
 9097        });
 9098    });
 9099    editor.update_in(cx, |editor, window, cx| {
 9100        assert_text_with_selections(
 9101            editor,
 9102            indoc! {r#"
 9103                use mod1::mod2::{mod3, mod4};
 9104
 9105                fn fn_1(param1: bool, param2: &str) {
 9106                    let var1 = "hel«lo woˇ»rld";
 9107                }
 9108            "#},
 9109            cx,
 9110        );
 9111        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9112        assert_text_with_selections(
 9113            editor,
 9114            indoc! {r#"
 9115                use mod1::mod2::{mod3, mod4};
 9116
 9117                fn fn_1(param1: bool, param2: &str) {
 9118                    let var1 = "«ˇhello world»";
 9119                }
 9120            "#},
 9121            cx,
 9122        );
 9123    });
 9124
 9125    // Test 5: Expansion beyond string
 9126    editor.update_in(cx, |editor, window, cx| {
 9127        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9128        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9129        assert_text_with_selections(
 9130            editor,
 9131            indoc! {r#"
 9132                use mod1::mod2::{mod3, mod4};
 9133
 9134                fn fn_1(param1: bool, param2: &str) {
 9135                    «ˇlet var1 = "hello world";»
 9136                }
 9137            "#},
 9138            cx,
 9139        );
 9140    });
 9141}
 9142
 9143#[gpui::test]
 9144async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9145    init_test(cx, |_| {});
 9146
 9147    let mut cx = EditorTestContext::new(cx).await;
 9148
 9149    let language = Arc::new(Language::new(
 9150        LanguageConfig::default(),
 9151        Some(tree_sitter_rust::LANGUAGE.into()),
 9152    ));
 9153
 9154    cx.update_buffer(|buffer, cx| {
 9155        buffer.set_language(Some(language), cx);
 9156    });
 9157
 9158    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9159    cx.update_editor(|editor, window, cx| {
 9160        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9161    });
 9162
 9163    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9164
 9165    cx.set_state(indoc! { r#"fn a() {
 9166          // what
 9167          // a
 9168          // ˇlong
 9169          // method
 9170          // I
 9171          // sure
 9172          // hope
 9173          // it
 9174          // works
 9175    }"# });
 9176
 9177    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
 9178    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 9179    cx.update(|_, cx| {
 9180        multi_buffer.update(cx, |multi_buffer, cx| {
 9181            multi_buffer.set_excerpts_for_path(
 9182                PathKey::for_buffer(&buffer, cx),
 9183                buffer,
 9184                [Point::new(1, 0)..Point::new(1, 0)],
 9185                3,
 9186                cx,
 9187            );
 9188        });
 9189    });
 9190
 9191    let editor2 = cx.new_window_entity(|window, cx| {
 9192        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 9193    });
 9194
 9195    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
 9196    cx.update_editor(|editor, window, cx| {
 9197        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9198            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
 9199        })
 9200    });
 9201
 9202    cx.assert_editor_state(indoc! { "
 9203        fn a() {
 9204              // what
 9205              // a
 9206        ˇ      // long
 9207              // method"});
 9208
 9209    cx.update_editor(|editor, window, cx| {
 9210        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9211    });
 9212
 9213    // Although we could potentially make the action work when the syntax node
 9214    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
 9215    // did. Maybe we could also expand the excerpt to contain the range?
 9216    cx.assert_editor_state(indoc! { "
 9217        fn a() {
 9218              // what
 9219              // a
 9220        ˇ      // long
 9221              // method"});
 9222}
 9223
 9224#[gpui::test]
 9225async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9226    init_test(cx, |_| {});
 9227
 9228    let base_text = r#"
 9229        impl A {
 9230            // this is an uncommitted comment
 9231
 9232            fn b() {
 9233                c();
 9234            }
 9235
 9236            // this is another uncommitted comment
 9237
 9238            fn d() {
 9239                // e
 9240                // f
 9241            }
 9242        }
 9243
 9244        fn g() {
 9245            // h
 9246        }
 9247    "#
 9248    .unindent();
 9249
 9250    let text = r#"
 9251        ˇimpl A {
 9252
 9253            fn b() {
 9254                c();
 9255            }
 9256
 9257            fn d() {
 9258                // e
 9259                // f
 9260            }
 9261        }
 9262
 9263        fn g() {
 9264            // h
 9265        }
 9266    "#
 9267    .unindent();
 9268
 9269    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9270    cx.set_state(&text);
 9271    cx.set_head_text(&base_text);
 9272    cx.update_editor(|editor, window, cx| {
 9273        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9274    });
 9275
 9276    cx.assert_state_with_diff(
 9277        "
 9278        ˇimpl A {
 9279      -     // this is an uncommitted comment
 9280
 9281            fn b() {
 9282                c();
 9283            }
 9284
 9285      -     // this is another uncommitted comment
 9286      -
 9287            fn d() {
 9288                // e
 9289                // f
 9290            }
 9291        }
 9292
 9293        fn g() {
 9294            // h
 9295        }
 9296    "
 9297        .unindent(),
 9298    );
 9299
 9300    let expected_display_text = "
 9301        impl A {
 9302            // this is an uncommitted comment
 9303
 9304            fn b() {
 9305 9306            }
 9307
 9308            // this is another uncommitted comment
 9309
 9310            fn d() {
 9311 9312            }
 9313        }
 9314
 9315        fn g() {
 9316 9317        }
 9318        "
 9319    .unindent();
 9320
 9321    cx.update_editor(|editor, window, cx| {
 9322        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9323        assert_eq!(editor.display_text(cx), expected_display_text);
 9324    });
 9325}
 9326
 9327#[gpui::test]
 9328async fn test_autoindent(cx: &mut TestAppContext) {
 9329    init_test(cx, |_| {});
 9330
 9331    let language = Arc::new(
 9332        Language::new(
 9333            LanguageConfig {
 9334                brackets: BracketPairConfig {
 9335                    pairs: vec![
 9336                        BracketPair {
 9337                            start: "{".to_string(),
 9338                            end: "}".to_string(),
 9339                            close: false,
 9340                            surround: false,
 9341                            newline: true,
 9342                        },
 9343                        BracketPair {
 9344                            start: "(".to_string(),
 9345                            end: ")".to_string(),
 9346                            close: false,
 9347                            surround: false,
 9348                            newline: true,
 9349                        },
 9350                    ],
 9351                    ..Default::default()
 9352                },
 9353                ..Default::default()
 9354            },
 9355            Some(tree_sitter_rust::LANGUAGE.into()),
 9356        )
 9357        .with_indents_query(
 9358            r#"
 9359                (_ "(" ")" @end) @indent
 9360                (_ "{" "}" @end) @indent
 9361            "#,
 9362        )
 9363        .unwrap(),
 9364    );
 9365
 9366    let text = "fn a() {}";
 9367
 9368    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9369    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9370    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9371    editor
 9372        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9373        .await;
 9374
 9375    editor.update_in(cx, |editor, window, cx| {
 9376        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9377            s.select_ranges([5..5, 8..8, 9..9])
 9378        });
 9379        editor.newline(&Newline, window, cx);
 9380        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9381        assert_eq!(
 9382            editor.selections.ranges(cx),
 9383            &[
 9384                Point::new(1, 4)..Point::new(1, 4),
 9385                Point::new(3, 4)..Point::new(3, 4),
 9386                Point::new(5, 0)..Point::new(5, 0)
 9387            ]
 9388        );
 9389    });
 9390}
 9391
 9392#[gpui::test]
 9393async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9394    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9395
 9396    let language = Arc::new(
 9397        Language::new(
 9398            LanguageConfig {
 9399                brackets: BracketPairConfig {
 9400                    pairs: vec![
 9401                        BracketPair {
 9402                            start: "{".to_string(),
 9403                            end: "}".to_string(),
 9404                            close: false,
 9405                            surround: false,
 9406                            newline: true,
 9407                        },
 9408                        BracketPair {
 9409                            start: "(".to_string(),
 9410                            end: ")".to_string(),
 9411                            close: false,
 9412                            surround: false,
 9413                            newline: true,
 9414                        },
 9415                    ],
 9416                    ..Default::default()
 9417                },
 9418                ..Default::default()
 9419            },
 9420            Some(tree_sitter_rust::LANGUAGE.into()),
 9421        )
 9422        .with_indents_query(
 9423            r#"
 9424                (_ "(" ")" @end) @indent
 9425                (_ "{" "}" @end) @indent
 9426            "#,
 9427        )
 9428        .unwrap(),
 9429    );
 9430
 9431    let text = "fn a() {}";
 9432
 9433    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9434    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9435    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9436    editor
 9437        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9438        .await;
 9439
 9440    editor.update_in(cx, |editor, window, cx| {
 9441        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9442            s.select_ranges([5..5, 8..8, 9..9])
 9443        });
 9444        editor.newline(&Newline, window, cx);
 9445        assert_eq!(
 9446            editor.text(cx),
 9447            indoc!(
 9448                "
 9449                fn a(
 9450
 9451                ) {
 9452
 9453                }
 9454                "
 9455            )
 9456        );
 9457        assert_eq!(
 9458            editor.selections.ranges(cx),
 9459            &[
 9460                Point::new(1, 0)..Point::new(1, 0),
 9461                Point::new(3, 0)..Point::new(3, 0),
 9462                Point::new(5, 0)..Point::new(5, 0)
 9463            ]
 9464        );
 9465    });
 9466}
 9467
 9468#[gpui::test]
 9469async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 9470    init_test(cx, |settings| {
 9471        settings.defaults.auto_indent = Some(true);
 9472        settings.languages.0.insert(
 9473            "python".into(),
 9474            LanguageSettingsContent {
 9475                auto_indent: Some(false),
 9476                ..Default::default()
 9477            },
 9478        );
 9479    });
 9480
 9481    let mut cx = EditorTestContext::new(cx).await;
 9482
 9483    let injected_language = Arc::new(
 9484        Language::new(
 9485            LanguageConfig {
 9486                brackets: BracketPairConfig {
 9487                    pairs: vec![
 9488                        BracketPair {
 9489                            start: "{".to_string(),
 9490                            end: "}".to_string(),
 9491                            close: false,
 9492                            surround: false,
 9493                            newline: true,
 9494                        },
 9495                        BracketPair {
 9496                            start: "(".to_string(),
 9497                            end: ")".to_string(),
 9498                            close: true,
 9499                            surround: false,
 9500                            newline: true,
 9501                        },
 9502                    ],
 9503                    ..Default::default()
 9504                },
 9505                name: "python".into(),
 9506                ..Default::default()
 9507            },
 9508            Some(tree_sitter_python::LANGUAGE.into()),
 9509        )
 9510        .with_indents_query(
 9511            r#"
 9512                (_ "(" ")" @end) @indent
 9513                (_ "{" "}" @end) @indent
 9514            "#,
 9515        )
 9516        .unwrap(),
 9517    );
 9518
 9519    let language = Arc::new(
 9520        Language::new(
 9521            LanguageConfig {
 9522                brackets: BracketPairConfig {
 9523                    pairs: vec![
 9524                        BracketPair {
 9525                            start: "{".to_string(),
 9526                            end: "}".to_string(),
 9527                            close: false,
 9528                            surround: false,
 9529                            newline: true,
 9530                        },
 9531                        BracketPair {
 9532                            start: "(".to_string(),
 9533                            end: ")".to_string(),
 9534                            close: true,
 9535                            surround: false,
 9536                            newline: true,
 9537                        },
 9538                    ],
 9539                    ..Default::default()
 9540                },
 9541                name: LanguageName::new("rust"),
 9542                ..Default::default()
 9543            },
 9544            Some(tree_sitter_rust::LANGUAGE.into()),
 9545        )
 9546        .with_indents_query(
 9547            r#"
 9548                (_ "(" ")" @end) @indent
 9549                (_ "{" "}" @end) @indent
 9550            "#,
 9551        )
 9552        .unwrap()
 9553        .with_injection_query(
 9554            r#"
 9555            (macro_invocation
 9556                macro: (identifier) @_macro_name
 9557                (token_tree) @injection.content
 9558                (#set! injection.language "python"))
 9559           "#,
 9560        )
 9561        .unwrap(),
 9562    );
 9563
 9564    cx.language_registry().add(injected_language);
 9565    cx.language_registry().add(language.clone());
 9566
 9567    cx.update_buffer(|buffer, cx| {
 9568        buffer.set_language(Some(language), cx);
 9569    });
 9570
 9571    cx.set_state(r#"struct A {ˇ}"#);
 9572
 9573    cx.update_editor(|editor, window, cx| {
 9574        editor.newline(&Default::default(), window, cx);
 9575    });
 9576
 9577    cx.assert_editor_state(indoc!(
 9578        "struct A {
 9579            ˇ
 9580        }"
 9581    ));
 9582
 9583    cx.set_state(r#"select_biased!(ˇ)"#);
 9584
 9585    cx.update_editor(|editor, window, cx| {
 9586        editor.newline(&Default::default(), window, cx);
 9587        editor.handle_input("def ", window, cx);
 9588        editor.handle_input("(", window, cx);
 9589        editor.newline(&Default::default(), window, cx);
 9590        editor.handle_input("a", window, cx);
 9591    });
 9592
 9593    cx.assert_editor_state(indoc!(
 9594        "select_biased!(
 9595        def (
 9596 9597        )
 9598        )"
 9599    ));
 9600}
 9601
 9602#[gpui::test]
 9603async fn test_autoindent_selections(cx: &mut TestAppContext) {
 9604    init_test(cx, |_| {});
 9605
 9606    {
 9607        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9608        cx.set_state(indoc! {"
 9609            impl A {
 9610
 9611                fn b() {}
 9612
 9613            «fn c() {
 9614
 9615            }ˇ»
 9616            }
 9617        "});
 9618
 9619        cx.update_editor(|editor, window, cx| {
 9620            editor.autoindent(&Default::default(), window, cx);
 9621        });
 9622
 9623        cx.assert_editor_state(indoc! {"
 9624            impl A {
 9625
 9626                fn b() {}
 9627
 9628                «fn c() {
 9629
 9630                }ˇ»
 9631            }
 9632        "});
 9633    }
 9634
 9635    {
 9636        let mut cx = EditorTestContext::new_multibuffer(
 9637            cx,
 9638            [indoc! { "
 9639                impl A {
 9640                «
 9641                // a
 9642                fn b(){}
 9643                »
 9644                «
 9645                    }
 9646                    fn c(){}
 9647                »
 9648            "}],
 9649        );
 9650
 9651        let buffer = cx.update_editor(|editor, _, cx| {
 9652            let buffer = editor.buffer().update(cx, |buffer, _| {
 9653                buffer.all_buffers().iter().next().unwrap().clone()
 9654            });
 9655            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 9656            buffer
 9657        });
 9658
 9659        cx.run_until_parked();
 9660        cx.update_editor(|editor, window, cx| {
 9661            editor.select_all(&Default::default(), window, cx);
 9662            editor.autoindent(&Default::default(), window, cx)
 9663        });
 9664        cx.run_until_parked();
 9665
 9666        cx.update(|_, cx| {
 9667            assert_eq!(
 9668                buffer.read(cx).text(),
 9669                indoc! { "
 9670                    impl A {
 9671
 9672                        // a
 9673                        fn b(){}
 9674
 9675
 9676                    }
 9677                    fn c(){}
 9678
 9679                " }
 9680            )
 9681        });
 9682    }
 9683}
 9684
 9685#[gpui::test]
 9686async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 9687    init_test(cx, |_| {});
 9688
 9689    let mut cx = EditorTestContext::new(cx).await;
 9690
 9691    let language = Arc::new(Language::new(
 9692        LanguageConfig {
 9693            brackets: BracketPairConfig {
 9694                pairs: vec![
 9695                    BracketPair {
 9696                        start: "{".to_string(),
 9697                        end: "}".to_string(),
 9698                        close: true,
 9699                        surround: true,
 9700                        newline: true,
 9701                    },
 9702                    BracketPair {
 9703                        start: "(".to_string(),
 9704                        end: ")".to_string(),
 9705                        close: true,
 9706                        surround: true,
 9707                        newline: true,
 9708                    },
 9709                    BracketPair {
 9710                        start: "/*".to_string(),
 9711                        end: " */".to_string(),
 9712                        close: true,
 9713                        surround: true,
 9714                        newline: true,
 9715                    },
 9716                    BracketPair {
 9717                        start: "[".to_string(),
 9718                        end: "]".to_string(),
 9719                        close: false,
 9720                        surround: false,
 9721                        newline: true,
 9722                    },
 9723                    BracketPair {
 9724                        start: "\"".to_string(),
 9725                        end: "\"".to_string(),
 9726                        close: true,
 9727                        surround: true,
 9728                        newline: false,
 9729                    },
 9730                    BracketPair {
 9731                        start: "<".to_string(),
 9732                        end: ">".to_string(),
 9733                        close: false,
 9734                        surround: true,
 9735                        newline: true,
 9736                    },
 9737                ],
 9738                ..Default::default()
 9739            },
 9740            autoclose_before: "})]".to_string(),
 9741            ..Default::default()
 9742        },
 9743        Some(tree_sitter_rust::LANGUAGE.into()),
 9744    ));
 9745
 9746    cx.language_registry().add(language.clone());
 9747    cx.update_buffer(|buffer, cx| {
 9748        buffer.set_language(Some(language), cx);
 9749    });
 9750
 9751    cx.set_state(
 9752        &r#"
 9753            🏀ˇ
 9754            εˇ
 9755            ❤️ˇ
 9756        "#
 9757        .unindent(),
 9758    );
 9759
 9760    // autoclose multiple nested brackets at multiple cursors
 9761    cx.update_editor(|editor, window, cx| {
 9762        editor.handle_input("{", window, cx);
 9763        editor.handle_input("{", window, cx);
 9764        editor.handle_input("{", window, cx);
 9765    });
 9766    cx.assert_editor_state(
 9767        &"
 9768            🏀{{{ˇ}}}
 9769            ε{{{ˇ}}}
 9770            ❤️{{{ˇ}}}
 9771        "
 9772        .unindent(),
 9773    );
 9774
 9775    // insert a different closing bracket
 9776    cx.update_editor(|editor, window, cx| {
 9777        editor.handle_input(")", window, cx);
 9778    });
 9779    cx.assert_editor_state(
 9780        &"
 9781            🏀{{{)ˇ}}}
 9782            ε{{{)ˇ}}}
 9783            ❤️{{{)ˇ}}}
 9784        "
 9785        .unindent(),
 9786    );
 9787
 9788    // skip over the auto-closed brackets when typing a closing bracket
 9789    cx.update_editor(|editor, window, cx| {
 9790        editor.move_right(&MoveRight, window, cx);
 9791        editor.handle_input("}", window, cx);
 9792        editor.handle_input("}", window, cx);
 9793        editor.handle_input("}", window, cx);
 9794    });
 9795    cx.assert_editor_state(
 9796        &"
 9797            🏀{{{)}}}}ˇ
 9798            ε{{{)}}}}ˇ
 9799            ❤️{{{)}}}}ˇ
 9800        "
 9801        .unindent(),
 9802    );
 9803
 9804    // autoclose multi-character pairs
 9805    cx.set_state(
 9806        &"
 9807            ˇ
 9808            ˇ
 9809        "
 9810        .unindent(),
 9811    );
 9812    cx.update_editor(|editor, window, cx| {
 9813        editor.handle_input("/", window, cx);
 9814        editor.handle_input("*", window, cx);
 9815    });
 9816    cx.assert_editor_state(
 9817        &"
 9818            /*ˇ */
 9819            /*ˇ */
 9820        "
 9821        .unindent(),
 9822    );
 9823
 9824    // one cursor autocloses a multi-character pair, one cursor
 9825    // does not autoclose.
 9826    cx.set_state(
 9827        &"
 9828 9829            ˇ
 9830        "
 9831        .unindent(),
 9832    );
 9833    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 9834    cx.assert_editor_state(
 9835        &"
 9836            /*ˇ */
 9837 9838        "
 9839        .unindent(),
 9840    );
 9841
 9842    // Don't autoclose if the next character isn't whitespace and isn't
 9843    // listed in the language's "autoclose_before" section.
 9844    cx.set_state("ˇa b");
 9845    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9846    cx.assert_editor_state("{ˇa b");
 9847
 9848    // Don't autoclose if `close` is false for the bracket pair
 9849    cx.set_state("ˇ");
 9850    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 9851    cx.assert_editor_state("");
 9852
 9853    // Surround with brackets if text is selected
 9854    cx.set_state("«aˇ» b");
 9855    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9856    cx.assert_editor_state("{«aˇ»} b");
 9857
 9858    // Autoclose when not immediately after a word character
 9859    cx.set_state("a ˇ");
 9860    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9861    cx.assert_editor_state("a \"ˇ\"");
 9862
 9863    // Autoclose pair where the start and end characters are the same
 9864    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9865    cx.assert_editor_state("a \"\"ˇ");
 9866
 9867    // Don't autoclose when immediately after a word character
 9868    cx.set_state("");
 9869    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9870    cx.assert_editor_state("a\"ˇ");
 9871
 9872    // Do autoclose when after a non-word character
 9873    cx.set_state("");
 9874    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9875    cx.assert_editor_state("{\"ˇ\"");
 9876
 9877    // Non identical pairs autoclose regardless of preceding character
 9878    cx.set_state("");
 9879    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9880    cx.assert_editor_state("a{ˇ}");
 9881
 9882    // Don't autoclose pair if autoclose is disabled
 9883    cx.set_state("ˇ");
 9884    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9885    cx.assert_editor_state("");
 9886
 9887    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 9888    cx.set_state("«aˇ» b");
 9889    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9890    cx.assert_editor_state("<«aˇ»> b");
 9891}
 9892
 9893#[gpui::test]
 9894async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 9895    init_test(cx, |settings| {
 9896        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9897    });
 9898
 9899    let mut cx = EditorTestContext::new(cx).await;
 9900
 9901    let language = Arc::new(Language::new(
 9902        LanguageConfig {
 9903            brackets: BracketPairConfig {
 9904                pairs: vec![
 9905                    BracketPair {
 9906                        start: "{".to_string(),
 9907                        end: "}".to_string(),
 9908                        close: true,
 9909                        surround: true,
 9910                        newline: true,
 9911                    },
 9912                    BracketPair {
 9913                        start: "(".to_string(),
 9914                        end: ")".to_string(),
 9915                        close: true,
 9916                        surround: true,
 9917                        newline: true,
 9918                    },
 9919                    BracketPair {
 9920                        start: "[".to_string(),
 9921                        end: "]".to_string(),
 9922                        close: false,
 9923                        surround: false,
 9924                        newline: true,
 9925                    },
 9926                ],
 9927                ..Default::default()
 9928            },
 9929            autoclose_before: "})]".to_string(),
 9930            ..Default::default()
 9931        },
 9932        Some(tree_sitter_rust::LANGUAGE.into()),
 9933    ));
 9934
 9935    cx.language_registry().add(language.clone());
 9936    cx.update_buffer(|buffer, cx| {
 9937        buffer.set_language(Some(language), cx);
 9938    });
 9939
 9940    cx.set_state(
 9941        &"
 9942            ˇ
 9943            ˇ
 9944            ˇ
 9945        "
 9946        .unindent(),
 9947    );
 9948
 9949    // ensure only matching closing brackets are skipped over
 9950    cx.update_editor(|editor, window, cx| {
 9951        editor.handle_input("}", window, cx);
 9952        editor.move_left(&MoveLeft, window, cx);
 9953        editor.handle_input(")", window, cx);
 9954        editor.move_left(&MoveLeft, window, cx);
 9955    });
 9956    cx.assert_editor_state(
 9957        &"
 9958            ˇ)}
 9959            ˇ)}
 9960            ˇ)}
 9961        "
 9962        .unindent(),
 9963    );
 9964
 9965    // skip-over closing brackets at multiple cursors
 9966    cx.update_editor(|editor, window, cx| {
 9967        editor.handle_input(")", window, cx);
 9968        editor.handle_input("}", window, cx);
 9969    });
 9970    cx.assert_editor_state(
 9971        &"
 9972            )}ˇ
 9973            )}ˇ
 9974            )}ˇ
 9975        "
 9976        .unindent(),
 9977    );
 9978
 9979    // ignore non-close brackets
 9980    cx.update_editor(|editor, window, cx| {
 9981        editor.handle_input("]", window, cx);
 9982        editor.move_left(&MoveLeft, window, cx);
 9983        editor.handle_input("]", window, cx);
 9984    });
 9985    cx.assert_editor_state(
 9986        &"
 9987            )}]ˇ]
 9988            )}]ˇ]
 9989            )}]ˇ]
 9990        "
 9991        .unindent(),
 9992    );
 9993}
 9994
 9995#[gpui::test]
 9996async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 9997    init_test(cx, |_| {});
 9998
 9999    let mut cx = EditorTestContext::new(cx).await;
10000
10001    let html_language = Arc::new(
10002        Language::new(
10003            LanguageConfig {
10004                name: "HTML".into(),
10005                brackets: BracketPairConfig {
10006                    pairs: vec![
10007                        BracketPair {
10008                            start: "<".into(),
10009                            end: ">".into(),
10010                            close: true,
10011                            ..Default::default()
10012                        },
10013                        BracketPair {
10014                            start: "{".into(),
10015                            end: "}".into(),
10016                            close: true,
10017                            ..Default::default()
10018                        },
10019                        BracketPair {
10020                            start: "(".into(),
10021                            end: ")".into(),
10022                            close: true,
10023                            ..Default::default()
10024                        },
10025                    ],
10026                    ..Default::default()
10027                },
10028                autoclose_before: "})]>".into(),
10029                ..Default::default()
10030            },
10031            Some(tree_sitter_html::LANGUAGE.into()),
10032        )
10033        .with_injection_query(
10034            r#"
10035            (script_element
10036                (raw_text) @injection.content
10037                (#set! injection.language "javascript"))
10038            "#,
10039        )
10040        .unwrap(),
10041    );
10042
10043    let javascript_language = Arc::new(Language::new(
10044        LanguageConfig {
10045            name: "JavaScript".into(),
10046            brackets: BracketPairConfig {
10047                pairs: vec![
10048                    BracketPair {
10049                        start: "/*".into(),
10050                        end: " */".into(),
10051                        close: true,
10052                        ..Default::default()
10053                    },
10054                    BracketPair {
10055                        start: "{".into(),
10056                        end: "}".into(),
10057                        close: true,
10058                        ..Default::default()
10059                    },
10060                    BracketPair {
10061                        start: "(".into(),
10062                        end: ")".into(),
10063                        close: true,
10064                        ..Default::default()
10065                    },
10066                ],
10067                ..Default::default()
10068            },
10069            autoclose_before: "})]>".into(),
10070            ..Default::default()
10071        },
10072        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10073    ));
10074
10075    cx.language_registry().add(html_language.clone());
10076    cx.language_registry().add(javascript_language);
10077    cx.executor().run_until_parked();
10078
10079    cx.update_buffer(|buffer, cx| {
10080        buffer.set_language(Some(html_language), cx);
10081    });
10082
10083    cx.set_state(
10084        &r#"
10085            <body>ˇ
10086                <script>
10087                    var x = 1;ˇ
10088                </script>
10089            </body>ˇ
10090        "#
10091        .unindent(),
10092    );
10093
10094    // Precondition: different languages are active at different locations.
10095    cx.update_editor(|editor, window, cx| {
10096        let snapshot = editor.snapshot(window, cx);
10097        let cursors = editor.selections.ranges::<usize>(cx);
10098        let languages = cursors
10099            .iter()
10100            .map(|c| snapshot.language_at(c.start).unwrap().name())
10101            .collect::<Vec<_>>();
10102        assert_eq!(
10103            languages,
10104            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10105        );
10106    });
10107
10108    // Angle brackets autoclose in HTML, but not JavaScript.
10109    cx.update_editor(|editor, window, cx| {
10110        editor.handle_input("<", window, cx);
10111        editor.handle_input("a", window, cx);
10112    });
10113    cx.assert_editor_state(
10114        &r#"
10115            <body><aˇ>
10116                <script>
10117                    var x = 1;<aˇ
10118                </script>
10119            </body><aˇ>
10120        "#
10121        .unindent(),
10122    );
10123
10124    // Curly braces and parens autoclose in both HTML and JavaScript.
10125    cx.update_editor(|editor, window, cx| {
10126        editor.handle_input(" b=", window, cx);
10127        editor.handle_input("{", window, cx);
10128        editor.handle_input("c", window, cx);
10129        editor.handle_input("(", window, cx);
10130    });
10131    cx.assert_editor_state(
10132        &r#"
10133            <body><a b={c(ˇ)}>
10134                <script>
10135                    var x = 1;<a b={c(ˇ)}
10136                </script>
10137            </body><a b={c(ˇ)}>
10138        "#
10139        .unindent(),
10140    );
10141
10142    // Brackets that were already autoclosed are skipped.
10143    cx.update_editor(|editor, window, cx| {
10144        editor.handle_input(")", window, cx);
10145        editor.handle_input("d", window, cx);
10146        editor.handle_input("}", window, cx);
10147    });
10148    cx.assert_editor_state(
10149        &r#"
10150            <body><a b={c()d}ˇ>
10151                <script>
10152                    var x = 1;<a b={c()d}ˇ
10153                </script>
10154            </body><a b={c()d}ˇ>
10155        "#
10156        .unindent(),
10157    );
10158    cx.update_editor(|editor, window, cx| {
10159        editor.handle_input(">", window, cx);
10160    });
10161    cx.assert_editor_state(
10162        &r#"
10163            <body><a b={c()d}>ˇ
10164                <script>
10165                    var x = 1;<a b={c()d}>ˇ
10166                </script>
10167            </body><a b={c()d}>ˇ
10168        "#
10169        .unindent(),
10170    );
10171
10172    // Reset
10173    cx.set_state(
10174        &r#"
10175            <body>ˇ
10176                <script>
10177                    var x = 1;ˇ
10178                </script>
10179            </body>ˇ
10180        "#
10181        .unindent(),
10182    );
10183
10184    cx.update_editor(|editor, window, cx| {
10185        editor.handle_input("<", window, cx);
10186    });
10187    cx.assert_editor_state(
10188        &r#"
10189            <body><ˇ>
10190                <script>
10191                    var x = 1;<ˇ
10192                </script>
10193            </body><ˇ>
10194        "#
10195        .unindent(),
10196    );
10197
10198    // When backspacing, the closing angle brackets are removed.
10199    cx.update_editor(|editor, window, cx| {
10200        editor.backspace(&Backspace, window, cx);
10201    });
10202    cx.assert_editor_state(
10203        &r#"
10204            <body>ˇ
10205                <script>
10206                    var x = 1;ˇ
10207                </script>
10208            </body>ˇ
10209        "#
10210        .unindent(),
10211    );
10212
10213    // Block comments autoclose in JavaScript, but not HTML.
10214    cx.update_editor(|editor, window, cx| {
10215        editor.handle_input("/", window, cx);
10216        editor.handle_input("*", window, cx);
10217    });
10218    cx.assert_editor_state(
10219        &r#"
10220            <body>/*ˇ
10221                <script>
10222                    var x = 1;/*ˇ */
10223                </script>
10224            </body>/*ˇ
10225        "#
10226        .unindent(),
10227    );
10228}
10229
10230#[gpui::test]
10231async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10232    init_test(cx, |_| {});
10233
10234    let mut cx = EditorTestContext::new(cx).await;
10235
10236    let rust_language = Arc::new(
10237        Language::new(
10238            LanguageConfig {
10239                name: "Rust".into(),
10240                brackets: serde_json::from_value(json!([
10241                    { "start": "{", "end": "}", "close": true, "newline": true },
10242                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10243                ]))
10244                .unwrap(),
10245                autoclose_before: "})]>".into(),
10246                ..Default::default()
10247            },
10248            Some(tree_sitter_rust::LANGUAGE.into()),
10249        )
10250        .with_override_query("(string_literal) @string")
10251        .unwrap(),
10252    );
10253
10254    cx.language_registry().add(rust_language.clone());
10255    cx.update_buffer(|buffer, cx| {
10256        buffer.set_language(Some(rust_language), cx);
10257    });
10258
10259    cx.set_state(
10260        &r#"
10261            let x = ˇ
10262        "#
10263        .unindent(),
10264    );
10265
10266    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10267    cx.update_editor(|editor, window, cx| {
10268        editor.handle_input("\"", window, cx);
10269    });
10270    cx.assert_editor_state(
10271        &r#"
10272            let x = "ˇ"
10273        "#
10274        .unindent(),
10275    );
10276
10277    // Inserting another quotation mark. The cursor moves across the existing
10278    // automatically-inserted quotation mark.
10279    cx.update_editor(|editor, window, cx| {
10280        editor.handle_input("\"", window, cx);
10281    });
10282    cx.assert_editor_state(
10283        &r#"
10284            let x = ""ˇ
10285        "#
10286        .unindent(),
10287    );
10288
10289    // Reset
10290    cx.set_state(
10291        &r#"
10292            let x = ˇ
10293        "#
10294        .unindent(),
10295    );
10296
10297    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
10298    cx.update_editor(|editor, window, cx| {
10299        editor.handle_input("\"", window, cx);
10300        editor.handle_input(" ", window, cx);
10301        editor.move_left(&Default::default(), window, cx);
10302        editor.handle_input("\\", window, cx);
10303        editor.handle_input("\"", window, cx);
10304    });
10305    cx.assert_editor_state(
10306        &r#"
10307            let x = "\"ˇ "
10308        "#
10309        .unindent(),
10310    );
10311
10312    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10313    // mark. Nothing is inserted.
10314    cx.update_editor(|editor, window, cx| {
10315        editor.move_right(&Default::default(), window, cx);
10316        editor.handle_input("\"", window, cx);
10317    });
10318    cx.assert_editor_state(
10319        &r#"
10320            let x = "\" "ˇ
10321        "#
10322        .unindent(),
10323    );
10324}
10325
10326#[gpui::test]
10327async fn test_surround_with_pair(cx: &mut TestAppContext) {
10328    init_test(cx, |_| {});
10329
10330    let language = Arc::new(Language::new(
10331        LanguageConfig {
10332            brackets: BracketPairConfig {
10333                pairs: vec![
10334                    BracketPair {
10335                        start: "{".to_string(),
10336                        end: "}".to_string(),
10337                        close: true,
10338                        surround: true,
10339                        newline: true,
10340                    },
10341                    BracketPair {
10342                        start: "/* ".to_string(),
10343                        end: "*/".to_string(),
10344                        close: true,
10345                        surround: true,
10346                        ..Default::default()
10347                    },
10348                ],
10349                ..Default::default()
10350            },
10351            ..Default::default()
10352        },
10353        Some(tree_sitter_rust::LANGUAGE.into()),
10354    ));
10355
10356    let text = r#"
10357        a
10358        b
10359        c
10360    "#
10361    .unindent();
10362
10363    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10364    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10365    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10366    editor
10367        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10368        .await;
10369
10370    editor.update_in(cx, |editor, window, cx| {
10371        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10372            s.select_display_ranges([
10373                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10374                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10375                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
10376            ])
10377        });
10378
10379        editor.handle_input("{", window, cx);
10380        editor.handle_input("{", window, cx);
10381        editor.handle_input("{", window, cx);
10382        assert_eq!(
10383            editor.text(cx),
10384            "
10385                {{{a}}}
10386                {{{b}}}
10387                {{{c}}}
10388            "
10389            .unindent()
10390        );
10391        assert_eq!(
10392            editor.selections.display_ranges(cx),
10393            [
10394                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
10395                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
10396                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
10397            ]
10398        );
10399
10400        editor.undo(&Undo, window, cx);
10401        editor.undo(&Undo, window, cx);
10402        editor.undo(&Undo, window, cx);
10403        assert_eq!(
10404            editor.text(cx),
10405            "
10406                a
10407                b
10408                c
10409            "
10410            .unindent()
10411        );
10412        assert_eq!(
10413            editor.selections.display_ranges(cx),
10414            [
10415                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10416                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10417                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10418            ]
10419        );
10420
10421        // Ensure inserting the first character of a multi-byte bracket pair
10422        // doesn't surround the selections with the bracket.
10423        editor.handle_input("/", window, cx);
10424        assert_eq!(
10425            editor.text(cx),
10426            "
10427                /
10428                /
10429                /
10430            "
10431            .unindent()
10432        );
10433        assert_eq!(
10434            editor.selections.display_ranges(cx),
10435            [
10436                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10437                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10438                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10439            ]
10440        );
10441
10442        editor.undo(&Undo, window, cx);
10443        assert_eq!(
10444            editor.text(cx),
10445            "
10446                a
10447                b
10448                c
10449            "
10450            .unindent()
10451        );
10452        assert_eq!(
10453            editor.selections.display_ranges(cx),
10454            [
10455                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10456                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10457                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10458            ]
10459        );
10460
10461        // Ensure inserting the last character of a multi-byte bracket pair
10462        // doesn't surround the selections with the bracket.
10463        editor.handle_input("*", window, cx);
10464        assert_eq!(
10465            editor.text(cx),
10466            "
10467                *
10468                *
10469                *
10470            "
10471            .unindent()
10472        );
10473        assert_eq!(
10474            editor.selections.display_ranges(cx),
10475            [
10476                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10477                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10478                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10479            ]
10480        );
10481    });
10482}
10483
10484#[gpui::test]
10485async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
10486    init_test(cx, |_| {});
10487
10488    let language = Arc::new(Language::new(
10489        LanguageConfig {
10490            brackets: BracketPairConfig {
10491                pairs: vec![BracketPair {
10492                    start: "{".to_string(),
10493                    end: "}".to_string(),
10494                    close: true,
10495                    surround: true,
10496                    newline: true,
10497                }],
10498                ..Default::default()
10499            },
10500            autoclose_before: "}".to_string(),
10501            ..Default::default()
10502        },
10503        Some(tree_sitter_rust::LANGUAGE.into()),
10504    ));
10505
10506    let text = r#"
10507        a
10508        b
10509        c
10510    "#
10511    .unindent();
10512
10513    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10514    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10515    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10516    editor
10517        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10518        .await;
10519
10520    editor.update_in(cx, |editor, window, cx| {
10521        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10522            s.select_ranges([
10523                Point::new(0, 1)..Point::new(0, 1),
10524                Point::new(1, 1)..Point::new(1, 1),
10525                Point::new(2, 1)..Point::new(2, 1),
10526            ])
10527        });
10528
10529        editor.handle_input("{", window, cx);
10530        editor.handle_input("{", window, cx);
10531        editor.handle_input("_", window, cx);
10532        assert_eq!(
10533            editor.text(cx),
10534            "
10535                a{{_}}
10536                b{{_}}
10537                c{{_}}
10538            "
10539            .unindent()
10540        );
10541        assert_eq!(
10542            editor.selections.ranges::<Point>(cx),
10543            [
10544                Point::new(0, 4)..Point::new(0, 4),
10545                Point::new(1, 4)..Point::new(1, 4),
10546                Point::new(2, 4)..Point::new(2, 4)
10547            ]
10548        );
10549
10550        editor.backspace(&Default::default(), window, cx);
10551        editor.backspace(&Default::default(), window, cx);
10552        assert_eq!(
10553            editor.text(cx),
10554            "
10555                a{}
10556                b{}
10557                c{}
10558            "
10559            .unindent()
10560        );
10561        assert_eq!(
10562            editor.selections.ranges::<Point>(cx),
10563            [
10564                Point::new(0, 2)..Point::new(0, 2),
10565                Point::new(1, 2)..Point::new(1, 2),
10566                Point::new(2, 2)..Point::new(2, 2)
10567            ]
10568        );
10569
10570        editor.delete_to_previous_word_start(&Default::default(), window, cx);
10571        assert_eq!(
10572            editor.text(cx),
10573            "
10574                a
10575                b
10576                c
10577            "
10578            .unindent()
10579        );
10580        assert_eq!(
10581            editor.selections.ranges::<Point>(cx),
10582            [
10583                Point::new(0, 1)..Point::new(0, 1),
10584                Point::new(1, 1)..Point::new(1, 1),
10585                Point::new(2, 1)..Point::new(2, 1)
10586            ]
10587        );
10588    });
10589}
10590
10591#[gpui::test]
10592async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
10593    init_test(cx, |settings| {
10594        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10595    });
10596
10597    let mut cx = EditorTestContext::new(cx).await;
10598
10599    let language = Arc::new(Language::new(
10600        LanguageConfig {
10601            brackets: BracketPairConfig {
10602                pairs: vec![
10603                    BracketPair {
10604                        start: "{".to_string(),
10605                        end: "}".to_string(),
10606                        close: true,
10607                        surround: true,
10608                        newline: true,
10609                    },
10610                    BracketPair {
10611                        start: "(".to_string(),
10612                        end: ")".to_string(),
10613                        close: true,
10614                        surround: true,
10615                        newline: true,
10616                    },
10617                    BracketPair {
10618                        start: "[".to_string(),
10619                        end: "]".to_string(),
10620                        close: false,
10621                        surround: true,
10622                        newline: true,
10623                    },
10624                ],
10625                ..Default::default()
10626            },
10627            autoclose_before: "})]".to_string(),
10628            ..Default::default()
10629        },
10630        Some(tree_sitter_rust::LANGUAGE.into()),
10631    ));
10632
10633    cx.language_registry().add(language.clone());
10634    cx.update_buffer(|buffer, cx| {
10635        buffer.set_language(Some(language), cx);
10636    });
10637
10638    cx.set_state(
10639        &"
10640            {(ˇ)}
10641            [[ˇ]]
10642            {(ˇ)}
10643        "
10644        .unindent(),
10645    );
10646
10647    cx.update_editor(|editor, window, cx| {
10648        editor.backspace(&Default::default(), window, cx);
10649        editor.backspace(&Default::default(), window, cx);
10650    });
10651
10652    cx.assert_editor_state(
10653        &"
10654            ˇ
10655            ˇ]]
10656            ˇ
10657        "
10658        .unindent(),
10659    );
10660
10661    cx.update_editor(|editor, window, cx| {
10662        editor.handle_input("{", window, cx);
10663        editor.handle_input("{", window, cx);
10664        editor.move_right(&MoveRight, window, cx);
10665        editor.move_right(&MoveRight, window, cx);
10666        editor.move_left(&MoveLeft, window, cx);
10667        editor.move_left(&MoveLeft, window, cx);
10668        editor.backspace(&Default::default(), window, cx);
10669    });
10670
10671    cx.assert_editor_state(
10672        &"
10673            {ˇ}
10674            {ˇ}]]
10675            {ˇ}
10676        "
10677        .unindent(),
10678    );
10679
10680    cx.update_editor(|editor, window, cx| {
10681        editor.backspace(&Default::default(), window, cx);
10682    });
10683
10684    cx.assert_editor_state(
10685        &"
10686            ˇ
10687            ˇ]]
10688            ˇ
10689        "
10690        .unindent(),
10691    );
10692}
10693
10694#[gpui::test]
10695async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
10696    init_test(cx, |_| {});
10697
10698    let language = Arc::new(Language::new(
10699        LanguageConfig::default(),
10700        Some(tree_sitter_rust::LANGUAGE.into()),
10701    ));
10702
10703    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
10704    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10705    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10706    editor
10707        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10708        .await;
10709
10710    editor.update_in(cx, |editor, window, cx| {
10711        editor.set_auto_replace_emoji_shortcode(true);
10712
10713        editor.handle_input("Hello ", window, cx);
10714        editor.handle_input(":wave", window, cx);
10715        assert_eq!(editor.text(cx), "Hello :wave".unindent());
10716
10717        editor.handle_input(":", window, cx);
10718        assert_eq!(editor.text(cx), "Hello 👋".unindent());
10719
10720        editor.handle_input(" :smile", window, cx);
10721        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
10722
10723        editor.handle_input(":", window, cx);
10724        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
10725
10726        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
10727        editor.handle_input(":wave", window, cx);
10728        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
10729
10730        editor.handle_input(":", window, cx);
10731        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
10732
10733        editor.handle_input(":1", window, cx);
10734        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
10735
10736        editor.handle_input(":", window, cx);
10737        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
10738
10739        // Ensure shortcode does not get replaced when it is part of a word
10740        editor.handle_input(" Test:wave", window, cx);
10741        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
10742
10743        editor.handle_input(":", window, cx);
10744        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
10745
10746        editor.set_auto_replace_emoji_shortcode(false);
10747
10748        // Ensure shortcode does not get replaced when auto replace is off
10749        editor.handle_input(" :wave", window, cx);
10750        assert_eq!(
10751            editor.text(cx),
10752            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
10753        );
10754
10755        editor.handle_input(":", window, cx);
10756        assert_eq!(
10757            editor.text(cx),
10758            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
10759        );
10760    });
10761}
10762
10763#[gpui::test]
10764async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
10765    init_test(cx, |_| {});
10766
10767    let (text, insertion_ranges) = marked_text_ranges(
10768        indoc! {"
10769            ˇ
10770        "},
10771        false,
10772    );
10773
10774    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
10775    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10776
10777    _ = editor.update_in(cx, |editor, window, cx| {
10778        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
10779
10780        editor
10781            .insert_snippet(&insertion_ranges, snippet, window, cx)
10782            .unwrap();
10783
10784        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
10785            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
10786            assert_eq!(editor.text(cx), expected_text);
10787            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
10788        }
10789
10790        assert(
10791            editor,
10792            cx,
10793            indoc! {"
10794            type «» =•
10795            "},
10796        );
10797
10798        assert!(editor.context_menu_visible(), "There should be a matches");
10799    });
10800}
10801
10802#[gpui::test]
10803async fn test_snippets(cx: &mut TestAppContext) {
10804    init_test(cx, |_| {});
10805
10806    let mut cx = EditorTestContext::new(cx).await;
10807
10808    cx.set_state(indoc! {"
10809        a.ˇ b
10810        a.ˇ b
10811        a.ˇ b
10812    "});
10813
10814    cx.update_editor(|editor, window, cx| {
10815        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
10816        let insertion_ranges = editor
10817            .selections
10818            .all(cx)
10819            .iter()
10820            .map(|s| s.range())
10821            .collect::<Vec<_>>();
10822        editor
10823            .insert_snippet(&insertion_ranges, snippet, window, cx)
10824            .unwrap();
10825    });
10826
10827    cx.assert_editor_state(indoc! {"
10828        a.f(«oneˇ», two, «threeˇ») b
10829        a.f(«oneˇ», two, «threeˇ») b
10830        a.f(«oneˇ», two, «threeˇ») b
10831    "});
10832
10833    // Can't move earlier than the first tab stop
10834    cx.update_editor(|editor, window, cx| {
10835        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10836    });
10837    cx.assert_editor_state(indoc! {"
10838        a.f(«oneˇ», two, «threeˇ») b
10839        a.f(«oneˇ», two, «threeˇ») b
10840        a.f(«oneˇ», two, «threeˇ») b
10841    "});
10842
10843    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10844    cx.assert_editor_state(indoc! {"
10845        a.f(one, «twoˇ», three) b
10846        a.f(one, «twoˇ», three) b
10847        a.f(one, «twoˇ», three) b
10848    "});
10849
10850    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
10851    cx.assert_editor_state(indoc! {"
10852        a.f(«oneˇ», two, «threeˇ») b
10853        a.f(«oneˇ», two, «threeˇ») b
10854        a.f(«oneˇ», two, «threeˇ») b
10855    "});
10856
10857    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10858    cx.assert_editor_state(indoc! {"
10859        a.f(one, «twoˇ», three) b
10860        a.f(one, «twoˇ», three) b
10861        a.f(one, «twoˇ», three) b
10862    "});
10863    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10864    cx.assert_editor_state(indoc! {"
10865        a.f(one, two, three)ˇ b
10866        a.f(one, two, three)ˇ b
10867        a.f(one, two, three)ˇ b
10868    "});
10869
10870    // As soon as the last tab stop is reached, snippet state is gone
10871    cx.update_editor(|editor, window, cx| {
10872        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10873    });
10874    cx.assert_editor_state(indoc! {"
10875        a.f(one, two, three)ˇ b
10876        a.f(one, two, three)ˇ b
10877        a.f(one, two, three)ˇ b
10878    "});
10879}
10880
10881#[gpui::test]
10882async fn test_snippet_indentation(cx: &mut TestAppContext) {
10883    init_test(cx, |_| {});
10884
10885    let mut cx = EditorTestContext::new(cx).await;
10886
10887    cx.update_editor(|editor, window, cx| {
10888        let snippet = Snippet::parse(indoc! {"
10889            /*
10890             * Multiline comment with leading indentation
10891             *
10892             * $1
10893             */
10894            $0"})
10895        .unwrap();
10896        let insertion_ranges = editor
10897            .selections
10898            .all(cx)
10899            .iter()
10900            .map(|s| s.range())
10901            .collect::<Vec<_>>();
10902        editor
10903            .insert_snippet(&insertion_ranges, snippet, window, cx)
10904            .unwrap();
10905    });
10906
10907    cx.assert_editor_state(indoc! {"
10908        /*
10909         * Multiline comment with leading indentation
10910         *
10911         * ˇ
10912         */
10913    "});
10914
10915    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10916    cx.assert_editor_state(indoc! {"
10917        /*
10918         * Multiline comment with leading indentation
10919         *
10920         *•
10921         */
10922        ˇ"});
10923}
10924
10925#[gpui::test]
10926async fn test_document_format_during_save(cx: &mut TestAppContext) {
10927    init_test(cx, |_| {});
10928
10929    let fs = FakeFs::new(cx.executor());
10930    fs.insert_file(path!("/file.rs"), Default::default()).await;
10931
10932    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
10933
10934    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10935    language_registry.add(rust_lang());
10936    let mut fake_servers = language_registry.register_fake_lsp(
10937        "Rust",
10938        FakeLspAdapter {
10939            capabilities: lsp::ServerCapabilities {
10940                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10941                ..Default::default()
10942            },
10943            ..Default::default()
10944        },
10945    );
10946
10947    let buffer = project
10948        .update(cx, |project, cx| {
10949            project.open_local_buffer(path!("/file.rs"), cx)
10950        })
10951        .await
10952        .unwrap();
10953
10954    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10955    let (editor, cx) = cx.add_window_view(|window, cx| {
10956        build_editor_with_project(project.clone(), buffer, window, cx)
10957    });
10958    editor.update_in(cx, |editor, window, cx| {
10959        editor.set_text("one\ntwo\nthree\n", window, cx)
10960    });
10961    assert!(cx.read(|cx| editor.is_dirty(cx)));
10962
10963    cx.executor().start_waiting();
10964    let fake_server = fake_servers.next().await.unwrap();
10965
10966    {
10967        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10968            move |params, _| async move {
10969                assert_eq!(
10970                    params.text_document.uri,
10971                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10972                );
10973                assert_eq!(params.options.tab_size, 4);
10974                Ok(Some(vec![lsp::TextEdit::new(
10975                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10976                    ", ".to_string(),
10977                )]))
10978            },
10979        );
10980        let save = editor
10981            .update_in(cx, |editor, window, cx| {
10982                editor.save(
10983                    SaveOptions {
10984                        format: true,
10985                        autosave: false,
10986                    },
10987                    project.clone(),
10988                    window,
10989                    cx,
10990                )
10991            })
10992            .unwrap();
10993        cx.executor().start_waiting();
10994        save.await;
10995
10996        assert_eq!(
10997            editor.update(cx, |editor, cx| editor.text(cx)),
10998            "one, two\nthree\n"
10999        );
11000        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11001    }
11002
11003    {
11004        editor.update_in(cx, |editor, window, cx| {
11005            editor.set_text("one\ntwo\nthree\n", window, cx)
11006        });
11007        assert!(cx.read(|cx| editor.is_dirty(cx)));
11008
11009        // Ensure we can still save even if formatting hangs.
11010        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11011            move |params, _| async move {
11012                assert_eq!(
11013                    params.text_document.uri,
11014                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11015                );
11016                futures::future::pending::<()>().await;
11017                unreachable!()
11018            },
11019        );
11020        let save = editor
11021            .update_in(cx, |editor, window, cx| {
11022                editor.save(
11023                    SaveOptions {
11024                        format: true,
11025                        autosave: false,
11026                    },
11027                    project.clone(),
11028                    window,
11029                    cx,
11030                )
11031            })
11032            .unwrap();
11033        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11034        cx.executor().start_waiting();
11035        save.await;
11036        assert_eq!(
11037            editor.update(cx, |editor, cx| editor.text(cx)),
11038            "one\ntwo\nthree\n"
11039        );
11040    }
11041
11042    // Set rust language override and assert overridden tabsize is sent to language server
11043    update_test_language_settings(cx, |settings| {
11044        settings.languages.0.insert(
11045            "Rust".into(),
11046            LanguageSettingsContent {
11047                tab_size: NonZeroU32::new(8),
11048                ..Default::default()
11049            },
11050        );
11051    });
11052
11053    {
11054        editor.update_in(cx, |editor, window, cx| {
11055            editor.set_text("somehting_new\n", window, cx)
11056        });
11057        assert!(cx.read(|cx| editor.is_dirty(cx)));
11058        let _formatting_request_signal = fake_server
11059            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11060                assert_eq!(
11061                    params.text_document.uri,
11062                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11063                );
11064                assert_eq!(params.options.tab_size, 8);
11065                Ok(Some(vec![]))
11066            });
11067        let save = editor
11068            .update_in(cx, |editor, window, cx| {
11069                editor.save(
11070                    SaveOptions {
11071                        format: true,
11072                        autosave: false,
11073                    },
11074                    project.clone(),
11075                    window,
11076                    cx,
11077                )
11078            })
11079            .unwrap();
11080        cx.executor().start_waiting();
11081        save.await;
11082    }
11083}
11084
11085#[gpui::test]
11086async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11087    init_test(cx, |settings| {
11088        settings.defaults.ensure_final_newline_on_save = Some(false);
11089    });
11090
11091    let fs = FakeFs::new(cx.executor());
11092    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11093
11094    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11095
11096    let buffer = project
11097        .update(cx, |project, cx| {
11098            project.open_local_buffer(path!("/file.txt"), cx)
11099        })
11100        .await
11101        .unwrap();
11102
11103    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11104    let (editor, cx) = cx.add_window_view(|window, cx| {
11105        build_editor_with_project(project.clone(), buffer, window, cx)
11106    });
11107    editor.update_in(cx, |editor, window, cx| {
11108        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11109            s.select_ranges([0..0])
11110        });
11111    });
11112    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11113
11114    editor.update_in(cx, |editor, window, cx| {
11115        editor.handle_input("\n", window, cx)
11116    });
11117    cx.run_until_parked();
11118    save(&editor, &project, cx).await;
11119    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11120
11121    editor.update_in(cx, |editor, window, cx| {
11122        editor.undo(&Default::default(), window, cx);
11123    });
11124    save(&editor, &project, cx).await;
11125    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11126
11127    editor.update_in(cx, |editor, window, cx| {
11128        editor.redo(&Default::default(), window, cx);
11129    });
11130    cx.run_until_parked();
11131    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11132
11133    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11134        let save = editor
11135            .update_in(cx, |editor, window, cx| {
11136                editor.save(
11137                    SaveOptions {
11138                        format: true,
11139                        autosave: false,
11140                    },
11141                    project.clone(),
11142                    window,
11143                    cx,
11144                )
11145            })
11146            .unwrap();
11147        cx.executor().start_waiting();
11148        save.await;
11149        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11150    }
11151}
11152
11153#[gpui::test]
11154async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
11155    init_test(cx, |_| {});
11156
11157    let cols = 4;
11158    let rows = 10;
11159    let sample_text_1 = sample_text(rows, cols, 'a');
11160    assert_eq!(
11161        sample_text_1,
11162        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11163    );
11164    let sample_text_2 = sample_text(rows, cols, 'l');
11165    assert_eq!(
11166        sample_text_2,
11167        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11168    );
11169    let sample_text_3 = sample_text(rows, cols, 'v');
11170    assert_eq!(
11171        sample_text_3,
11172        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11173    );
11174
11175    let fs = FakeFs::new(cx.executor());
11176    fs.insert_tree(
11177        path!("/a"),
11178        json!({
11179            "main.rs": sample_text_1,
11180            "other.rs": sample_text_2,
11181            "lib.rs": sample_text_3,
11182        }),
11183    )
11184    .await;
11185
11186    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11187    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11188    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11189
11190    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11191    language_registry.add(rust_lang());
11192    let mut fake_servers = language_registry.register_fake_lsp(
11193        "Rust",
11194        FakeLspAdapter {
11195            capabilities: lsp::ServerCapabilities {
11196                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11197                ..Default::default()
11198            },
11199            ..Default::default()
11200        },
11201    );
11202
11203    let worktree = project.update(cx, |project, cx| {
11204        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
11205        assert_eq!(worktrees.len(), 1);
11206        worktrees.pop().unwrap()
11207    });
11208    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11209
11210    let buffer_1 = project
11211        .update(cx, |project, cx| {
11212            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
11213        })
11214        .await
11215        .unwrap();
11216    let buffer_2 = project
11217        .update(cx, |project, cx| {
11218            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
11219        })
11220        .await
11221        .unwrap();
11222    let buffer_3 = project
11223        .update(cx, |project, cx| {
11224            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
11225        })
11226        .await
11227        .unwrap();
11228
11229    let multi_buffer = cx.new(|cx| {
11230        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11231        multi_buffer.push_excerpts(
11232            buffer_1.clone(),
11233            [
11234                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11235                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11236                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11237            ],
11238            cx,
11239        );
11240        multi_buffer.push_excerpts(
11241            buffer_2.clone(),
11242            [
11243                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11244                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11245                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11246            ],
11247            cx,
11248        );
11249        multi_buffer.push_excerpts(
11250            buffer_3.clone(),
11251            [
11252                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11253                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11254                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11255            ],
11256            cx,
11257        );
11258        multi_buffer
11259    });
11260    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
11261        Editor::new(
11262            EditorMode::full(),
11263            multi_buffer,
11264            Some(project.clone()),
11265            window,
11266            cx,
11267        )
11268    });
11269
11270    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11271        editor.change_selections(
11272            SelectionEffects::scroll(Autoscroll::Next),
11273            window,
11274            cx,
11275            |s| s.select_ranges(Some(1..2)),
11276        );
11277        editor.insert("|one|two|three|", window, cx);
11278    });
11279    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11280    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11281        editor.change_selections(
11282            SelectionEffects::scroll(Autoscroll::Next),
11283            window,
11284            cx,
11285            |s| s.select_ranges(Some(60..70)),
11286        );
11287        editor.insert("|four|five|six|", window, cx);
11288    });
11289    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11290
11291    // First two buffers should be edited, but not the third one.
11292    assert_eq!(
11293        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11294        "a|one|two|three|aa\nbbbb\ncccc\n\nffff\ngggg\n\njjjj\nllll\nmmmm\nnnnn|four|five|six|\nr\n\nuuuu\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}",
11295    );
11296    buffer_1.update(cx, |buffer, _| {
11297        assert!(buffer.is_dirty());
11298        assert_eq!(
11299            buffer.text(),
11300            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
11301        )
11302    });
11303    buffer_2.update(cx, |buffer, _| {
11304        assert!(buffer.is_dirty());
11305        assert_eq!(
11306            buffer.text(),
11307            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
11308        )
11309    });
11310    buffer_3.update(cx, |buffer, _| {
11311        assert!(!buffer.is_dirty());
11312        assert_eq!(buffer.text(), sample_text_3,)
11313    });
11314    cx.executor().run_until_parked();
11315
11316    cx.executor().start_waiting();
11317    let save = multi_buffer_editor
11318        .update_in(cx, |editor, window, cx| {
11319            editor.save(
11320                SaveOptions {
11321                    format: true,
11322                    autosave: false,
11323                },
11324                project.clone(),
11325                window,
11326                cx,
11327            )
11328        })
11329        .unwrap();
11330
11331    let fake_server = fake_servers.next().await.unwrap();
11332    fake_server
11333        .server
11334        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11335            Ok(Some(vec![lsp::TextEdit::new(
11336                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11337                format!("[{} formatted]", params.text_document.uri),
11338            )]))
11339        })
11340        .detach();
11341    save.await;
11342
11343    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11344    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11345    assert_eq!(
11346        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11347        uri!(
11348            "a|o[file:///a/main.rs formatted]bbbb\ncccc\n\nffff\ngggg\n\njjjj\n\nlll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|\nr\n\nuuuu\n\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}"
11349        ),
11350    );
11351    buffer_1.update(cx, |buffer, _| {
11352        assert!(!buffer.is_dirty());
11353        assert_eq!(
11354            buffer.text(),
11355            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11356        )
11357    });
11358    buffer_2.update(cx, |buffer, _| {
11359        assert!(!buffer.is_dirty());
11360        assert_eq!(
11361            buffer.text(),
11362            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11363        )
11364    });
11365    buffer_3.update(cx, |buffer, _| {
11366        assert!(!buffer.is_dirty());
11367        assert_eq!(buffer.text(), sample_text_3,)
11368    });
11369}
11370
11371#[gpui::test]
11372async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
11373    init_test(cx, |_| {});
11374
11375    let fs = FakeFs::new(cx.executor());
11376    fs.insert_tree(
11377        path!("/dir"),
11378        json!({
11379            "file1.rs": "fn main() { println!(\"hello\"); }",
11380            "file2.rs": "fn test() { println!(\"test\"); }",
11381            "file3.rs": "fn other() { println!(\"other\"); }\n",
11382        }),
11383    )
11384    .await;
11385
11386    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
11387    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11388    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11389
11390    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11391    language_registry.add(rust_lang());
11392
11393    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
11394    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11395
11396    // Open three buffers
11397    let buffer_1 = project
11398        .update(cx, |project, cx| {
11399            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
11400        })
11401        .await
11402        .unwrap();
11403    let buffer_2 = project
11404        .update(cx, |project, cx| {
11405            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
11406        })
11407        .await
11408        .unwrap();
11409    let buffer_3 = project
11410        .update(cx, |project, cx| {
11411            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
11412        })
11413        .await
11414        .unwrap();
11415
11416    // Create a multi-buffer with all three buffers
11417    let multi_buffer = cx.new(|cx| {
11418        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11419        multi_buffer.push_excerpts(
11420            buffer_1.clone(),
11421            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11422            cx,
11423        );
11424        multi_buffer.push_excerpts(
11425            buffer_2.clone(),
11426            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11427            cx,
11428        );
11429        multi_buffer.push_excerpts(
11430            buffer_3.clone(),
11431            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11432            cx,
11433        );
11434        multi_buffer
11435    });
11436
11437    let editor = cx.new_window_entity(|window, cx| {
11438        Editor::new(
11439            EditorMode::full(),
11440            multi_buffer,
11441            Some(project.clone()),
11442            window,
11443            cx,
11444        )
11445    });
11446
11447    // Edit only the first buffer
11448    editor.update_in(cx, |editor, window, cx| {
11449        editor.change_selections(
11450            SelectionEffects::scroll(Autoscroll::Next),
11451            window,
11452            cx,
11453            |s| s.select_ranges(Some(10..10)),
11454        );
11455        editor.insert("// edited", window, cx);
11456    });
11457
11458    // Verify that only buffer 1 is dirty
11459    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
11460    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11461    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11462
11463    // Get write counts after file creation (files were created with initial content)
11464    // We expect each file to have been written once during creation
11465    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
11466    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
11467    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
11468
11469    // Perform autosave
11470    let save_task = editor.update_in(cx, |editor, window, cx| {
11471        editor.save(
11472            SaveOptions {
11473                format: true,
11474                autosave: true,
11475            },
11476            project.clone(),
11477            window,
11478            cx,
11479        )
11480    });
11481    save_task.await.unwrap();
11482
11483    // Only the dirty buffer should have been saved
11484    assert_eq!(
11485        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11486        1,
11487        "Buffer 1 was dirty, so it should have been written once during autosave"
11488    );
11489    assert_eq!(
11490        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11491        0,
11492        "Buffer 2 was clean, so it should not have been written during autosave"
11493    );
11494    assert_eq!(
11495        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11496        0,
11497        "Buffer 3 was clean, so it should not have been written during autosave"
11498    );
11499
11500    // Verify buffer states after autosave
11501    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11502    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11503    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11504
11505    // Now perform a manual save (format = true)
11506    let save_task = editor.update_in(cx, |editor, window, cx| {
11507        editor.save(
11508            SaveOptions {
11509                format: true,
11510                autosave: false,
11511            },
11512            project.clone(),
11513            window,
11514            cx,
11515        )
11516    });
11517    save_task.await.unwrap();
11518
11519    // During manual save, clean buffers don't get written to disk
11520    // They just get did_save called for language server notifications
11521    assert_eq!(
11522        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11523        1,
11524        "Buffer 1 should only have been written once total (during autosave, not manual save)"
11525    );
11526    assert_eq!(
11527        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11528        0,
11529        "Buffer 2 should not have been written at all"
11530    );
11531    assert_eq!(
11532        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11533        0,
11534        "Buffer 3 should not have been written at all"
11535    );
11536}
11537
11538async fn setup_range_format_test(
11539    cx: &mut TestAppContext,
11540) -> (
11541    Entity<Project>,
11542    Entity<Editor>,
11543    &mut gpui::VisualTestContext,
11544    lsp::FakeLanguageServer,
11545) {
11546    init_test(cx, |_| {});
11547
11548    let fs = FakeFs::new(cx.executor());
11549    fs.insert_file(path!("/file.rs"), Default::default()).await;
11550
11551    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11552
11553    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11554    language_registry.add(rust_lang());
11555    let mut fake_servers = language_registry.register_fake_lsp(
11556        "Rust",
11557        FakeLspAdapter {
11558            capabilities: lsp::ServerCapabilities {
11559                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
11560                ..lsp::ServerCapabilities::default()
11561            },
11562            ..FakeLspAdapter::default()
11563        },
11564    );
11565
11566    let buffer = project
11567        .update(cx, |project, cx| {
11568            project.open_local_buffer(path!("/file.rs"), cx)
11569        })
11570        .await
11571        .unwrap();
11572
11573    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11574    let (editor, cx) = cx.add_window_view(|window, cx| {
11575        build_editor_with_project(project.clone(), buffer, window, cx)
11576    });
11577
11578    cx.executor().start_waiting();
11579    let fake_server = fake_servers.next().await.unwrap();
11580
11581    (project, editor, cx, fake_server)
11582}
11583
11584#[gpui::test]
11585async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
11586    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11587
11588    editor.update_in(cx, |editor, window, cx| {
11589        editor.set_text("one\ntwo\nthree\n", window, cx)
11590    });
11591    assert!(cx.read(|cx| editor.is_dirty(cx)));
11592
11593    let save = editor
11594        .update_in(cx, |editor, window, cx| {
11595            editor.save(
11596                SaveOptions {
11597                    format: true,
11598                    autosave: false,
11599                },
11600                project.clone(),
11601                window,
11602                cx,
11603            )
11604        })
11605        .unwrap();
11606    fake_server
11607        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11608            assert_eq!(
11609                params.text_document.uri,
11610                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11611            );
11612            assert_eq!(params.options.tab_size, 4);
11613            Ok(Some(vec![lsp::TextEdit::new(
11614                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11615                ", ".to_string(),
11616            )]))
11617        })
11618        .next()
11619        .await;
11620    cx.executor().start_waiting();
11621    save.await;
11622    assert_eq!(
11623        editor.update(cx, |editor, cx| editor.text(cx)),
11624        "one, two\nthree\n"
11625    );
11626    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11627}
11628
11629#[gpui::test]
11630async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
11631    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11632
11633    editor.update_in(cx, |editor, window, cx| {
11634        editor.set_text("one\ntwo\nthree\n", window, cx)
11635    });
11636    assert!(cx.read(|cx| editor.is_dirty(cx)));
11637
11638    // Test that save still works when formatting hangs
11639    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
11640        move |params, _| async move {
11641            assert_eq!(
11642                params.text_document.uri,
11643                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11644            );
11645            futures::future::pending::<()>().await;
11646            unreachable!()
11647        },
11648    );
11649    let save = editor
11650        .update_in(cx, |editor, window, cx| {
11651            editor.save(
11652                SaveOptions {
11653                    format: true,
11654                    autosave: false,
11655                },
11656                project.clone(),
11657                window,
11658                cx,
11659            )
11660        })
11661        .unwrap();
11662    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11663    cx.executor().start_waiting();
11664    save.await;
11665    assert_eq!(
11666        editor.update(cx, |editor, cx| editor.text(cx)),
11667        "one\ntwo\nthree\n"
11668    );
11669    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11670}
11671
11672#[gpui::test]
11673async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
11674    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11675
11676    // Buffer starts clean, no formatting should be requested
11677    let save = editor
11678        .update_in(cx, |editor, window, cx| {
11679            editor.save(
11680                SaveOptions {
11681                    format: false,
11682                    autosave: false,
11683                },
11684                project.clone(),
11685                window,
11686                cx,
11687            )
11688        })
11689        .unwrap();
11690    let _pending_format_request = fake_server
11691        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
11692            panic!("Should not be invoked");
11693        })
11694        .next();
11695    cx.executor().start_waiting();
11696    save.await;
11697    cx.run_until_parked();
11698}
11699
11700#[gpui::test]
11701async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
11702    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11703
11704    // Set Rust language override and assert overridden tabsize is sent to language server
11705    update_test_language_settings(cx, |settings| {
11706        settings.languages.0.insert(
11707            "Rust".into(),
11708            LanguageSettingsContent {
11709                tab_size: NonZeroU32::new(8),
11710                ..Default::default()
11711            },
11712        );
11713    });
11714
11715    editor.update_in(cx, |editor, window, cx| {
11716        editor.set_text("something_new\n", window, cx)
11717    });
11718    assert!(cx.read(|cx| editor.is_dirty(cx)));
11719    let save = editor
11720        .update_in(cx, |editor, window, cx| {
11721            editor.save(
11722                SaveOptions {
11723                    format: true,
11724                    autosave: false,
11725                },
11726                project.clone(),
11727                window,
11728                cx,
11729            )
11730        })
11731        .unwrap();
11732    fake_server
11733        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11734            assert_eq!(
11735                params.text_document.uri,
11736                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11737            );
11738            assert_eq!(params.options.tab_size, 8);
11739            Ok(Some(Vec::new()))
11740        })
11741        .next()
11742        .await;
11743    save.await;
11744}
11745
11746#[gpui::test]
11747async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
11748    init_test(cx, |settings| {
11749        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
11750            Formatter::LanguageServer { name: None },
11751        )))
11752    });
11753
11754    let fs = FakeFs::new(cx.executor());
11755    fs.insert_file(path!("/file.rs"), Default::default()).await;
11756
11757    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11758
11759    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11760    language_registry.add(Arc::new(Language::new(
11761        LanguageConfig {
11762            name: "Rust".into(),
11763            matcher: LanguageMatcher {
11764                path_suffixes: vec!["rs".to_string()],
11765                ..Default::default()
11766            },
11767            ..LanguageConfig::default()
11768        },
11769        Some(tree_sitter_rust::LANGUAGE.into()),
11770    )));
11771    update_test_language_settings(cx, |settings| {
11772        // Enable Prettier formatting for the same buffer, and ensure
11773        // LSP is called instead of Prettier.
11774        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
11775    });
11776    let mut fake_servers = language_registry.register_fake_lsp(
11777        "Rust",
11778        FakeLspAdapter {
11779            capabilities: lsp::ServerCapabilities {
11780                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11781                ..Default::default()
11782            },
11783            ..Default::default()
11784        },
11785    );
11786
11787    let buffer = project
11788        .update(cx, |project, cx| {
11789            project.open_local_buffer(path!("/file.rs"), cx)
11790        })
11791        .await
11792        .unwrap();
11793
11794    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11795    let (editor, cx) = cx.add_window_view(|window, cx| {
11796        build_editor_with_project(project.clone(), buffer, window, cx)
11797    });
11798    editor.update_in(cx, |editor, window, cx| {
11799        editor.set_text("one\ntwo\nthree\n", window, cx)
11800    });
11801
11802    cx.executor().start_waiting();
11803    let fake_server = fake_servers.next().await.unwrap();
11804
11805    let format = editor
11806        .update_in(cx, |editor, window, cx| {
11807            editor.perform_format(
11808                project.clone(),
11809                FormatTrigger::Manual,
11810                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11811                window,
11812                cx,
11813            )
11814        })
11815        .unwrap();
11816    fake_server
11817        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11818            assert_eq!(
11819                params.text_document.uri,
11820                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11821            );
11822            assert_eq!(params.options.tab_size, 4);
11823            Ok(Some(vec![lsp::TextEdit::new(
11824                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11825                ", ".to_string(),
11826            )]))
11827        })
11828        .next()
11829        .await;
11830    cx.executor().start_waiting();
11831    format.await;
11832    assert_eq!(
11833        editor.update(cx, |editor, cx| editor.text(cx)),
11834        "one, two\nthree\n"
11835    );
11836
11837    editor.update_in(cx, |editor, window, cx| {
11838        editor.set_text("one\ntwo\nthree\n", window, cx)
11839    });
11840    // Ensure we don't lock if formatting hangs.
11841    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11842        move |params, _| async move {
11843            assert_eq!(
11844                params.text_document.uri,
11845                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11846            );
11847            futures::future::pending::<()>().await;
11848            unreachable!()
11849        },
11850    );
11851    let format = editor
11852        .update_in(cx, |editor, window, cx| {
11853            editor.perform_format(
11854                project,
11855                FormatTrigger::Manual,
11856                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11857                window,
11858                cx,
11859            )
11860        })
11861        .unwrap();
11862    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11863    cx.executor().start_waiting();
11864    format.await;
11865    assert_eq!(
11866        editor.update(cx, |editor, cx| editor.text(cx)),
11867        "one\ntwo\nthree\n"
11868    );
11869}
11870
11871#[gpui::test]
11872async fn test_multiple_formatters(cx: &mut TestAppContext) {
11873    init_test(cx, |settings| {
11874        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
11875        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
11876            Formatter::LanguageServer { name: None },
11877            Formatter::CodeActions(
11878                [
11879                    ("code-action-1".into(), true),
11880                    ("code-action-2".into(), true),
11881                ]
11882                .into_iter()
11883                .collect(),
11884            ),
11885        ])))
11886    });
11887
11888    let fs = FakeFs::new(cx.executor());
11889    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
11890        .await;
11891
11892    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11893    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11894    language_registry.add(rust_lang());
11895
11896    let mut fake_servers = language_registry.register_fake_lsp(
11897        "Rust",
11898        FakeLspAdapter {
11899            capabilities: lsp::ServerCapabilities {
11900                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11901                execute_command_provider: Some(lsp::ExecuteCommandOptions {
11902                    commands: vec!["the-command-for-code-action-1".into()],
11903                    ..Default::default()
11904                }),
11905                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
11906                ..Default::default()
11907            },
11908            ..Default::default()
11909        },
11910    );
11911
11912    let buffer = project
11913        .update(cx, |project, cx| {
11914            project.open_local_buffer(path!("/file.rs"), cx)
11915        })
11916        .await
11917        .unwrap();
11918
11919    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11920    let (editor, cx) = cx.add_window_view(|window, cx| {
11921        build_editor_with_project(project.clone(), buffer, window, cx)
11922    });
11923
11924    cx.executor().start_waiting();
11925
11926    let fake_server = fake_servers.next().await.unwrap();
11927    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11928        move |_params, _| async move {
11929            Ok(Some(vec![lsp::TextEdit::new(
11930                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11931                "applied-formatting\n".to_string(),
11932            )]))
11933        },
11934    );
11935    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
11936        move |params, _| async move {
11937            let requested_code_actions = params.context.only.expect("Expected code action request");
11938            assert_eq!(requested_code_actions.len(), 1);
11939
11940            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
11941            let code_action = match requested_code_actions[0].as_str() {
11942                "code-action-1" => lsp::CodeAction {
11943                    kind: Some("code-action-1".into()),
11944                    edit: Some(lsp::WorkspaceEdit::new(
11945                        [(
11946                            uri,
11947                            vec![lsp::TextEdit::new(
11948                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11949                                "applied-code-action-1-edit\n".to_string(),
11950                            )],
11951                        )]
11952                        .into_iter()
11953                        .collect(),
11954                    )),
11955                    command: Some(lsp::Command {
11956                        command: "the-command-for-code-action-1".into(),
11957                        ..Default::default()
11958                    }),
11959                    ..Default::default()
11960                },
11961                "code-action-2" => lsp::CodeAction {
11962                    kind: Some("code-action-2".into()),
11963                    edit: Some(lsp::WorkspaceEdit::new(
11964                        [(
11965                            uri,
11966                            vec![lsp::TextEdit::new(
11967                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11968                                "applied-code-action-2-edit\n".to_string(),
11969                            )],
11970                        )]
11971                        .into_iter()
11972                        .collect(),
11973                    )),
11974                    ..Default::default()
11975                },
11976                req => panic!("Unexpected code action request: {:?}", req),
11977            };
11978            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
11979                code_action,
11980            )]))
11981        },
11982    );
11983
11984    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
11985        move |params, _| async move { Ok(params) }
11986    });
11987
11988    let command_lock = Arc::new(futures::lock::Mutex::new(()));
11989    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
11990        let fake = fake_server.clone();
11991        let lock = command_lock.clone();
11992        move |params, _| {
11993            assert_eq!(params.command, "the-command-for-code-action-1");
11994            let fake = fake.clone();
11995            let lock = lock.clone();
11996            async move {
11997                lock.lock().await;
11998                fake.server
11999                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
12000                        label: None,
12001                        edit: lsp::WorkspaceEdit {
12002                            changes: Some(
12003                                [(
12004                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
12005                                    vec![lsp::TextEdit {
12006                                        range: lsp::Range::new(
12007                                            lsp::Position::new(0, 0),
12008                                            lsp::Position::new(0, 0),
12009                                        ),
12010                                        new_text: "applied-code-action-1-command\n".into(),
12011                                    }],
12012                                )]
12013                                .into_iter()
12014                                .collect(),
12015                            ),
12016                            ..Default::default()
12017                        },
12018                    })
12019                    .await
12020                    .into_response()
12021                    .unwrap();
12022                Ok(Some(json!(null)))
12023            }
12024        }
12025    });
12026
12027    cx.executor().start_waiting();
12028    editor
12029        .update_in(cx, |editor, window, cx| {
12030            editor.perform_format(
12031                project.clone(),
12032                FormatTrigger::Manual,
12033                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12034                window,
12035                cx,
12036            )
12037        })
12038        .unwrap()
12039        .await;
12040    editor.update(cx, |editor, cx| {
12041        assert_eq!(
12042            editor.text(cx),
12043            r#"
12044                applied-code-action-2-edit
12045                applied-code-action-1-command
12046                applied-code-action-1-edit
12047                applied-formatting
12048                one
12049                two
12050                three
12051            "#
12052            .unindent()
12053        );
12054    });
12055
12056    editor.update_in(cx, |editor, window, cx| {
12057        editor.undo(&Default::default(), window, cx);
12058        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12059    });
12060
12061    // Perform a manual edit while waiting for an LSP command
12062    // that's being run as part of a formatting code action.
12063    let lock_guard = command_lock.lock().await;
12064    let format = editor
12065        .update_in(cx, |editor, window, cx| {
12066            editor.perform_format(
12067                project.clone(),
12068                FormatTrigger::Manual,
12069                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12070                window,
12071                cx,
12072            )
12073        })
12074        .unwrap();
12075    cx.run_until_parked();
12076    editor.update(cx, |editor, cx| {
12077        assert_eq!(
12078            editor.text(cx),
12079            r#"
12080                applied-code-action-1-edit
12081                applied-formatting
12082                one
12083                two
12084                three
12085            "#
12086            .unindent()
12087        );
12088
12089        editor.buffer.update(cx, |buffer, cx| {
12090            let ix = buffer.len(cx);
12091            buffer.edit([(ix..ix, "edited\n")], None, cx);
12092        });
12093    });
12094
12095    // Allow the LSP command to proceed. Because the buffer was edited,
12096    // the second code action will not be run.
12097    drop(lock_guard);
12098    format.await;
12099    editor.update_in(cx, |editor, window, cx| {
12100        assert_eq!(
12101            editor.text(cx),
12102            r#"
12103                applied-code-action-1-command
12104                applied-code-action-1-edit
12105                applied-formatting
12106                one
12107                two
12108                three
12109                edited
12110            "#
12111            .unindent()
12112        );
12113
12114        // The manual edit is undone first, because it is the last thing the user did
12115        // (even though the command completed afterwards).
12116        editor.undo(&Default::default(), window, cx);
12117        assert_eq!(
12118            editor.text(cx),
12119            r#"
12120                applied-code-action-1-command
12121                applied-code-action-1-edit
12122                applied-formatting
12123                one
12124                two
12125                three
12126            "#
12127            .unindent()
12128        );
12129
12130        // All the formatting (including the command, which completed after the manual edit)
12131        // is undone together.
12132        editor.undo(&Default::default(), window, cx);
12133        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12134    });
12135}
12136
12137#[gpui::test]
12138async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12139    init_test(cx, |settings| {
12140        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
12141            Formatter::LanguageServer { name: None },
12142        ])))
12143    });
12144
12145    let fs = FakeFs::new(cx.executor());
12146    fs.insert_file(path!("/file.ts"), Default::default()).await;
12147
12148    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12149
12150    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12151    language_registry.add(Arc::new(Language::new(
12152        LanguageConfig {
12153            name: "TypeScript".into(),
12154            matcher: LanguageMatcher {
12155                path_suffixes: vec!["ts".to_string()],
12156                ..Default::default()
12157            },
12158            ..LanguageConfig::default()
12159        },
12160        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12161    )));
12162    update_test_language_settings(cx, |settings| {
12163        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12164    });
12165    let mut fake_servers = language_registry.register_fake_lsp(
12166        "TypeScript",
12167        FakeLspAdapter {
12168            capabilities: lsp::ServerCapabilities {
12169                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12170                ..Default::default()
12171            },
12172            ..Default::default()
12173        },
12174    );
12175
12176    let buffer = project
12177        .update(cx, |project, cx| {
12178            project.open_local_buffer(path!("/file.ts"), cx)
12179        })
12180        .await
12181        .unwrap();
12182
12183    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12184    let (editor, cx) = cx.add_window_view(|window, cx| {
12185        build_editor_with_project(project.clone(), buffer, window, cx)
12186    });
12187    editor.update_in(cx, |editor, window, cx| {
12188        editor.set_text(
12189            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12190            window,
12191            cx,
12192        )
12193    });
12194
12195    cx.executor().start_waiting();
12196    let fake_server = fake_servers.next().await.unwrap();
12197
12198    let format = editor
12199        .update_in(cx, |editor, window, cx| {
12200            editor.perform_code_action_kind(
12201                project.clone(),
12202                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12203                window,
12204                cx,
12205            )
12206        })
12207        .unwrap();
12208    fake_server
12209        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12210            assert_eq!(
12211                params.text_document.uri,
12212                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12213            );
12214            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12215                lsp::CodeAction {
12216                    title: "Organize Imports".to_string(),
12217                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12218                    edit: Some(lsp::WorkspaceEdit {
12219                        changes: Some(
12220                            [(
12221                                params.text_document.uri.clone(),
12222                                vec![lsp::TextEdit::new(
12223                                    lsp::Range::new(
12224                                        lsp::Position::new(1, 0),
12225                                        lsp::Position::new(2, 0),
12226                                    ),
12227                                    "".to_string(),
12228                                )],
12229                            )]
12230                            .into_iter()
12231                            .collect(),
12232                        ),
12233                        ..Default::default()
12234                    }),
12235                    ..Default::default()
12236                },
12237            )]))
12238        })
12239        .next()
12240        .await;
12241    cx.executor().start_waiting();
12242    format.await;
12243    assert_eq!(
12244        editor.update(cx, |editor, cx| editor.text(cx)),
12245        "import { a } from 'module';\n\nconst x = a;\n"
12246    );
12247
12248    editor.update_in(cx, |editor, window, cx| {
12249        editor.set_text(
12250            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12251            window,
12252            cx,
12253        )
12254    });
12255    // Ensure we don't lock if code action hangs.
12256    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12257        move |params, _| async move {
12258            assert_eq!(
12259                params.text_document.uri,
12260                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12261            );
12262            futures::future::pending::<()>().await;
12263            unreachable!()
12264        },
12265    );
12266    let format = editor
12267        .update_in(cx, |editor, window, cx| {
12268            editor.perform_code_action_kind(
12269                project,
12270                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12271                window,
12272                cx,
12273            )
12274        })
12275        .unwrap();
12276    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
12277    cx.executor().start_waiting();
12278    format.await;
12279    assert_eq!(
12280        editor.update(cx, |editor, cx| editor.text(cx)),
12281        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
12282    );
12283}
12284
12285#[gpui::test]
12286async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
12287    init_test(cx, |_| {});
12288
12289    let mut cx = EditorLspTestContext::new_rust(
12290        lsp::ServerCapabilities {
12291            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12292            ..Default::default()
12293        },
12294        cx,
12295    )
12296    .await;
12297
12298    cx.set_state(indoc! {"
12299        one.twoˇ
12300    "});
12301
12302    // The format request takes a long time. When it completes, it inserts
12303    // a newline and an indent before the `.`
12304    cx.lsp
12305        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12306            let executor = cx.background_executor().clone();
12307            async move {
12308                executor.timer(Duration::from_millis(100)).await;
12309                Ok(Some(vec![lsp::TextEdit {
12310                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12311                    new_text: "\n    ".into(),
12312                }]))
12313            }
12314        });
12315
12316    // Submit a format request.
12317    let format_1 = cx
12318        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12319        .unwrap();
12320    cx.executor().run_until_parked();
12321
12322    // Submit a second format request.
12323    let format_2 = cx
12324        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12325        .unwrap();
12326    cx.executor().run_until_parked();
12327
12328    // Wait for both format requests to complete
12329    cx.executor().advance_clock(Duration::from_millis(200));
12330    cx.executor().start_waiting();
12331    format_1.await.unwrap();
12332    cx.executor().start_waiting();
12333    format_2.await.unwrap();
12334
12335    // The formatting edits only happens once.
12336    cx.assert_editor_state(indoc! {"
12337        one
12338            .twoˇ
12339    "});
12340}
12341
12342#[gpui::test]
12343async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12344    init_test(cx, |settings| {
12345        settings.defaults.formatter = Some(SelectedFormatter::Auto)
12346    });
12347
12348    let mut cx = EditorLspTestContext::new_rust(
12349        lsp::ServerCapabilities {
12350            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12351            ..Default::default()
12352        },
12353        cx,
12354    )
12355    .await;
12356
12357    // Set up a buffer white some trailing whitespace and no trailing newline.
12358    cx.set_state(
12359        &[
12360            "one ",   //
12361            "twoˇ",   //
12362            "three ", //
12363            "four",   //
12364        ]
12365        .join("\n"),
12366    );
12367
12368    // Submit a format request.
12369    let format = cx
12370        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12371        .unwrap();
12372
12373    // Record which buffer changes have been sent to the language server
12374    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12375    cx.lsp
12376        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12377            let buffer_changes = buffer_changes.clone();
12378            move |params, _| {
12379                buffer_changes.lock().extend(
12380                    params
12381                        .content_changes
12382                        .into_iter()
12383                        .map(|e| (e.range.unwrap(), e.text)),
12384                );
12385            }
12386        });
12387
12388    // Handle formatting requests to the language server.
12389    cx.lsp
12390        .set_request_handler::<lsp::request::Formatting, _, _>({
12391            let buffer_changes = buffer_changes.clone();
12392            move |_, _| {
12393                // When formatting is requested, trailing whitespace has already been stripped,
12394                // and the trailing newline has already been added.
12395                assert_eq!(
12396                    &buffer_changes.lock()[1..],
12397                    &[
12398                        (
12399                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12400                            "".into()
12401                        ),
12402                        (
12403                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12404                            "".into()
12405                        ),
12406                        (
12407                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12408                            "\n".into()
12409                        ),
12410                    ]
12411                );
12412
12413                // Insert blank lines between each line of the buffer.
12414                async move {
12415                    Ok(Some(vec![
12416                        lsp::TextEdit {
12417                            range: lsp::Range::new(
12418                                lsp::Position::new(1, 0),
12419                                lsp::Position::new(1, 0),
12420                            ),
12421                            new_text: "\n".into(),
12422                        },
12423                        lsp::TextEdit {
12424                            range: lsp::Range::new(
12425                                lsp::Position::new(2, 0),
12426                                lsp::Position::new(2, 0),
12427                            ),
12428                            new_text: "\n".into(),
12429                        },
12430                    ]))
12431                }
12432            }
12433        });
12434
12435    // After formatting the buffer, the trailing whitespace is stripped,
12436    // a newline is appended, and the edits provided by the language server
12437    // have been applied.
12438    format.await.unwrap();
12439    cx.assert_editor_state(
12440        &[
12441            "one",   //
12442            "",      //
12443            "twoˇ",  //
12444            "",      //
12445            "three", //
12446            "four",  //
12447            "",      //
12448        ]
12449        .join("\n"),
12450    );
12451
12452    // Undoing the formatting undoes the trailing whitespace removal, the
12453    // trailing newline, and the LSP edits.
12454    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12455    cx.assert_editor_state(
12456        &[
12457            "one ",   //
12458            "twoˇ",   //
12459            "three ", //
12460            "four",   //
12461        ]
12462        .join("\n"),
12463    );
12464}
12465
12466#[gpui::test]
12467async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12468    cx: &mut TestAppContext,
12469) {
12470    init_test(cx, |_| {});
12471
12472    cx.update(|cx| {
12473        cx.update_global::<SettingsStore, _>(|settings, cx| {
12474            settings.update_user_settings(cx, |settings| {
12475                settings.editor.auto_signature_help = Some(true);
12476            });
12477        });
12478    });
12479
12480    let mut cx = EditorLspTestContext::new_rust(
12481        lsp::ServerCapabilities {
12482            signature_help_provider: Some(lsp::SignatureHelpOptions {
12483                ..Default::default()
12484            }),
12485            ..Default::default()
12486        },
12487        cx,
12488    )
12489    .await;
12490
12491    let language = Language::new(
12492        LanguageConfig {
12493            name: "Rust".into(),
12494            brackets: BracketPairConfig {
12495                pairs: vec![
12496                    BracketPair {
12497                        start: "{".to_string(),
12498                        end: "}".to_string(),
12499                        close: true,
12500                        surround: true,
12501                        newline: true,
12502                    },
12503                    BracketPair {
12504                        start: "(".to_string(),
12505                        end: ")".to_string(),
12506                        close: true,
12507                        surround: true,
12508                        newline: true,
12509                    },
12510                    BracketPair {
12511                        start: "/*".to_string(),
12512                        end: " */".to_string(),
12513                        close: true,
12514                        surround: true,
12515                        newline: true,
12516                    },
12517                    BracketPair {
12518                        start: "[".to_string(),
12519                        end: "]".to_string(),
12520                        close: false,
12521                        surround: false,
12522                        newline: true,
12523                    },
12524                    BracketPair {
12525                        start: "\"".to_string(),
12526                        end: "\"".to_string(),
12527                        close: true,
12528                        surround: true,
12529                        newline: false,
12530                    },
12531                    BracketPair {
12532                        start: "<".to_string(),
12533                        end: ">".to_string(),
12534                        close: false,
12535                        surround: true,
12536                        newline: true,
12537                    },
12538                ],
12539                ..Default::default()
12540            },
12541            autoclose_before: "})]".to_string(),
12542            ..Default::default()
12543        },
12544        Some(tree_sitter_rust::LANGUAGE.into()),
12545    );
12546    let language = Arc::new(language);
12547
12548    cx.language_registry().add(language.clone());
12549    cx.update_buffer(|buffer, cx| {
12550        buffer.set_language(Some(language), cx);
12551    });
12552
12553    cx.set_state(
12554        &r#"
12555            fn main() {
12556                sampleˇ
12557            }
12558        "#
12559        .unindent(),
12560    );
12561
12562    cx.update_editor(|editor, window, cx| {
12563        editor.handle_input("(", window, cx);
12564    });
12565    cx.assert_editor_state(
12566        &"
12567            fn main() {
12568                sample(ˇ)
12569            }
12570        "
12571        .unindent(),
12572    );
12573
12574    let mocked_response = lsp::SignatureHelp {
12575        signatures: vec![lsp::SignatureInformation {
12576            label: "fn sample(param1: u8, param2: u8)".to_string(),
12577            documentation: None,
12578            parameters: Some(vec![
12579                lsp::ParameterInformation {
12580                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12581                    documentation: None,
12582                },
12583                lsp::ParameterInformation {
12584                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12585                    documentation: None,
12586                },
12587            ]),
12588            active_parameter: None,
12589        }],
12590        active_signature: Some(0),
12591        active_parameter: Some(0),
12592    };
12593    handle_signature_help_request(&mut cx, mocked_response).await;
12594
12595    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12596        .await;
12597
12598    cx.editor(|editor, _, _| {
12599        let signature_help_state = editor.signature_help_state.popover().cloned();
12600        let signature = signature_help_state.unwrap();
12601        assert_eq!(
12602            signature.signatures[signature.current_signature].label,
12603            "fn sample(param1: u8, param2: u8)"
12604        );
12605    });
12606}
12607
12608#[gpui::test]
12609async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
12610    init_test(cx, |_| {});
12611
12612    cx.update(|cx| {
12613        cx.update_global::<SettingsStore, _>(|settings, cx| {
12614            settings.update_user_settings(cx, |settings| {
12615                settings.editor.auto_signature_help = Some(false);
12616                settings.editor.show_signature_help_after_edits = Some(false);
12617            });
12618        });
12619    });
12620
12621    let mut cx = EditorLspTestContext::new_rust(
12622        lsp::ServerCapabilities {
12623            signature_help_provider: Some(lsp::SignatureHelpOptions {
12624                ..Default::default()
12625            }),
12626            ..Default::default()
12627        },
12628        cx,
12629    )
12630    .await;
12631
12632    let language = Language::new(
12633        LanguageConfig {
12634            name: "Rust".into(),
12635            brackets: BracketPairConfig {
12636                pairs: vec![
12637                    BracketPair {
12638                        start: "{".to_string(),
12639                        end: "}".to_string(),
12640                        close: true,
12641                        surround: true,
12642                        newline: true,
12643                    },
12644                    BracketPair {
12645                        start: "(".to_string(),
12646                        end: ")".to_string(),
12647                        close: true,
12648                        surround: true,
12649                        newline: true,
12650                    },
12651                    BracketPair {
12652                        start: "/*".to_string(),
12653                        end: " */".to_string(),
12654                        close: true,
12655                        surround: true,
12656                        newline: true,
12657                    },
12658                    BracketPair {
12659                        start: "[".to_string(),
12660                        end: "]".to_string(),
12661                        close: false,
12662                        surround: false,
12663                        newline: true,
12664                    },
12665                    BracketPair {
12666                        start: "\"".to_string(),
12667                        end: "\"".to_string(),
12668                        close: true,
12669                        surround: true,
12670                        newline: false,
12671                    },
12672                    BracketPair {
12673                        start: "<".to_string(),
12674                        end: ">".to_string(),
12675                        close: false,
12676                        surround: true,
12677                        newline: true,
12678                    },
12679                ],
12680                ..Default::default()
12681            },
12682            autoclose_before: "})]".to_string(),
12683            ..Default::default()
12684        },
12685        Some(tree_sitter_rust::LANGUAGE.into()),
12686    );
12687    let language = Arc::new(language);
12688
12689    cx.language_registry().add(language.clone());
12690    cx.update_buffer(|buffer, cx| {
12691        buffer.set_language(Some(language), cx);
12692    });
12693
12694    // Ensure that signature_help is not called when no signature help is enabled.
12695    cx.set_state(
12696        &r#"
12697            fn main() {
12698                sampleˇ
12699            }
12700        "#
12701        .unindent(),
12702    );
12703    cx.update_editor(|editor, window, cx| {
12704        editor.handle_input("(", window, cx);
12705    });
12706    cx.assert_editor_state(
12707        &"
12708            fn main() {
12709                sample(ˇ)
12710            }
12711        "
12712        .unindent(),
12713    );
12714    cx.editor(|editor, _, _| {
12715        assert!(editor.signature_help_state.task().is_none());
12716    });
12717
12718    let mocked_response = lsp::SignatureHelp {
12719        signatures: vec![lsp::SignatureInformation {
12720            label: "fn sample(param1: u8, param2: u8)".to_string(),
12721            documentation: None,
12722            parameters: Some(vec![
12723                lsp::ParameterInformation {
12724                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12725                    documentation: None,
12726                },
12727                lsp::ParameterInformation {
12728                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12729                    documentation: None,
12730                },
12731            ]),
12732            active_parameter: None,
12733        }],
12734        active_signature: Some(0),
12735        active_parameter: Some(0),
12736    };
12737
12738    // Ensure that signature_help is called when enabled afte edits
12739    cx.update(|_, cx| {
12740        cx.update_global::<SettingsStore, _>(|settings, cx| {
12741            settings.update_user_settings(cx, |settings| {
12742                settings.editor.auto_signature_help = Some(false);
12743                settings.editor.show_signature_help_after_edits = Some(true);
12744            });
12745        });
12746    });
12747    cx.set_state(
12748        &r#"
12749            fn main() {
12750                sampleˇ
12751            }
12752        "#
12753        .unindent(),
12754    );
12755    cx.update_editor(|editor, window, cx| {
12756        editor.handle_input("(", window, cx);
12757    });
12758    cx.assert_editor_state(
12759        &"
12760            fn main() {
12761                sample(ˇ)
12762            }
12763        "
12764        .unindent(),
12765    );
12766    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12767    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12768        .await;
12769    cx.update_editor(|editor, _, _| {
12770        let signature_help_state = editor.signature_help_state.popover().cloned();
12771        assert!(signature_help_state.is_some());
12772        let signature = signature_help_state.unwrap();
12773        assert_eq!(
12774            signature.signatures[signature.current_signature].label,
12775            "fn sample(param1: u8, param2: u8)"
12776        );
12777        editor.signature_help_state = SignatureHelpState::default();
12778    });
12779
12780    // Ensure that signature_help is called when auto signature help override is enabled
12781    cx.update(|_, cx| {
12782        cx.update_global::<SettingsStore, _>(|settings, cx| {
12783            settings.update_user_settings(cx, |settings| {
12784                settings.editor.auto_signature_help = Some(true);
12785                settings.editor.show_signature_help_after_edits = Some(false);
12786            });
12787        });
12788    });
12789    cx.set_state(
12790        &r#"
12791            fn main() {
12792                sampleˇ
12793            }
12794        "#
12795        .unindent(),
12796    );
12797    cx.update_editor(|editor, window, cx| {
12798        editor.handle_input("(", window, cx);
12799    });
12800    cx.assert_editor_state(
12801        &"
12802            fn main() {
12803                sample(ˇ)
12804            }
12805        "
12806        .unindent(),
12807    );
12808    handle_signature_help_request(&mut cx, mocked_response).await;
12809    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12810        .await;
12811    cx.editor(|editor, _, _| {
12812        let signature_help_state = editor.signature_help_state.popover().cloned();
12813        assert!(signature_help_state.is_some());
12814        let signature = signature_help_state.unwrap();
12815        assert_eq!(
12816            signature.signatures[signature.current_signature].label,
12817            "fn sample(param1: u8, param2: u8)"
12818        );
12819    });
12820}
12821
12822#[gpui::test]
12823async fn test_signature_help(cx: &mut TestAppContext) {
12824    init_test(cx, |_| {});
12825    cx.update(|cx| {
12826        cx.update_global::<SettingsStore, _>(|settings, cx| {
12827            settings.update_user_settings(cx, |settings| {
12828                settings.editor.auto_signature_help = Some(true);
12829            });
12830        });
12831    });
12832
12833    let mut cx = EditorLspTestContext::new_rust(
12834        lsp::ServerCapabilities {
12835            signature_help_provider: Some(lsp::SignatureHelpOptions {
12836                ..Default::default()
12837            }),
12838            ..Default::default()
12839        },
12840        cx,
12841    )
12842    .await;
12843
12844    // A test that directly calls `show_signature_help`
12845    cx.update_editor(|editor, window, cx| {
12846        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12847    });
12848
12849    let mocked_response = lsp::SignatureHelp {
12850        signatures: vec![lsp::SignatureInformation {
12851            label: "fn sample(param1: u8, param2: u8)".to_string(),
12852            documentation: None,
12853            parameters: Some(vec![
12854                lsp::ParameterInformation {
12855                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12856                    documentation: None,
12857                },
12858                lsp::ParameterInformation {
12859                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12860                    documentation: None,
12861                },
12862            ]),
12863            active_parameter: None,
12864        }],
12865        active_signature: Some(0),
12866        active_parameter: Some(0),
12867    };
12868    handle_signature_help_request(&mut cx, mocked_response).await;
12869
12870    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12871        .await;
12872
12873    cx.editor(|editor, _, _| {
12874        let signature_help_state = editor.signature_help_state.popover().cloned();
12875        assert!(signature_help_state.is_some());
12876        let signature = signature_help_state.unwrap();
12877        assert_eq!(
12878            signature.signatures[signature.current_signature].label,
12879            "fn sample(param1: u8, param2: u8)"
12880        );
12881    });
12882
12883    // When exiting outside from inside the brackets, `signature_help` is closed.
12884    cx.set_state(indoc! {"
12885        fn main() {
12886            sample(ˇ);
12887        }
12888
12889        fn sample(param1: u8, param2: u8) {}
12890    "});
12891
12892    cx.update_editor(|editor, window, cx| {
12893        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12894            s.select_ranges([0..0])
12895        });
12896    });
12897
12898    let mocked_response = lsp::SignatureHelp {
12899        signatures: Vec::new(),
12900        active_signature: None,
12901        active_parameter: None,
12902    };
12903    handle_signature_help_request(&mut cx, mocked_response).await;
12904
12905    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
12906        .await;
12907
12908    cx.editor(|editor, _, _| {
12909        assert!(!editor.signature_help_state.is_shown());
12910    });
12911
12912    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
12913    cx.set_state(indoc! {"
12914        fn main() {
12915            sample(ˇ);
12916        }
12917
12918        fn sample(param1: u8, param2: u8) {}
12919    "});
12920
12921    let mocked_response = lsp::SignatureHelp {
12922        signatures: vec![lsp::SignatureInformation {
12923            label: "fn sample(param1: u8, param2: u8)".to_string(),
12924            documentation: None,
12925            parameters: Some(vec![
12926                lsp::ParameterInformation {
12927                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12928                    documentation: None,
12929                },
12930                lsp::ParameterInformation {
12931                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12932                    documentation: None,
12933                },
12934            ]),
12935            active_parameter: None,
12936        }],
12937        active_signature: Some(0),
12938        active_parameter: Some(0),
12939    };
12940    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12941    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12942        .await;
12943    cx.editor(|editor, _, _| {
12944        assert!(editor.signature_help_state.is_shown());
12945    });
12946
12947    // Restore the popover with more parameter input
12948    cx.set_state(indoc! {"
12949        fn main() {
12950            sample(param1, param2ˇ);
12951        }
12952
12953        fn sample(param1: u8, param2: u8) {}
12954    "});
12955
12956    let mocked_response = lsp::SignatureHelp {
12957        signatures: vec![lsp::SignatureInformation {
12958            label: "fn sample(param1: u8, param2: u8)".to_string(),
12959            documentation: None,
12960            parameters: Some(vec![
12961                lsp::ParameterInformation {
12962                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12963                    documentation: None,
12964                },
12965                lsp::ParameterInformation {
12966                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12967                    documentation: None,
12968                },
12969            ]),
12970            active_parameter: None,
12971        }],
12972        active_signature: Some(0),
12973        active_parameter: Some(1),
12974    };
12975    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12976    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12977        .await;
12978
12979    // When selecting a range, the popover is gone.
12980    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
12981    cx.update_editor(|editor, window, cx| {
12982        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12983            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
12984        })
12985    });
12986    cx.assert_editor_state(indoc! {"
12987        fn main() {
12988            sample(param1, «ˇparam2»);
12989        }
12990
12991        fn sample(param1: u8, param2: u8) {}
12992    "});
12993    cx.editor(|editor, _, _| {
12994        assert!(!editor.signature_help_state.is_shown());
12995    });
12996
12997    // When unselecting again, the popover is back if within the brackets.
12998    cx.update_editor(|editor, window, cx| {
12999        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13000            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13001        })
13002    });
13003    cx.assert_editor_state(indoc! {"
13004        fn main() {
13005            sample(param1, ˇparam2);
13006        }
13007
13008        fn sample(param1: u8, param2: u8) {}
13009    "});
13010    handle_signature_help_request(&mut cx, mocked_response).await;
13011    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13012        .await;
13013    cx.editor(|editor, _, _| {
13014        assert!(editor.signature_help_state.is_shown());
13015    });
13016
13017    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13018    cx.update_editor(|editor, window, cx| {
13019        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13020            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13021            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13022        })
13023    });
13024    cx.assert_editor_state(indoc! {"
13025        fn main() {
13026            sample(param1, ˇparam2);
13027        }
13028
13029        fn sample(param1: u8, param2: u8) {}
13030    "});
13031
13032    let mocked_response = lsp::SignatureHelp {
13033        signatures: vec![lsp::SignatureInformation {
13034            label: "fn sample(param1: u8, param2: u8)".to_string(),
13035            documentation: None,
13036            parameters: Some(vec![
13037                lsp::ParameterInformation {
13038                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13039                    documentation: None,
13040                },
13041                lsp::ParameterInformation {
13042                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13043                    documentation: None,
13044                },
13045            ]),
13046            active_parameter: None,
13047        }],
13048        active_signature: Some(0),
13049        active_parameter: Some(1),
13050    };
13051    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13052    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13053        .await;
13054    cx.update_editor(|editor, _, cx| {
13055        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13056    });
13057    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13058        .await;
13059    cx.update_editor(|editor, window, cx| {
13060        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13061            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13062        })
13063    });
13064    cx.assert_editor_state(indoc! {"
13065        fn main() {
13066            sample(param1, «ˇparam2»);
13067        }
13068
13069        fn sample(param1: u8, param2: u8) {}
13070    "});
13071    cx.update_editor(|editor, window, cx| {
13072        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13073            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13074        })
13075    });
13076    cx.assert_editor_state(indoc! {"
13077        fn main() {
13078            sample(param1, ˇparam2);
13079        }
13080
13081        fn sample(param1: u8, param2: u8) {}
13082    "});
13083    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13084        .await;
13085}
13086
13087#[gpui::test]
13088async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13089    init_test(cx, |_| {});
13090
13091    let mut cx = EditorLspTestContext::new_rust(
13092        lsp::ServerCapabilities {
13093            signature_help_provider: Some(lsp::SignatureHelpOptions {
13094                ..Default::default()
13095            }),
13096            ..Default::default()
13097        },
13098        cx,
13099    )
13100    .await;
13101
13102    cx.set_state(indoc! {"
13103        fn main() {
13104            overloadedˇ
13105        }
13106    "});
13107
13108    cx.update_editor(|editor, window, cx| {
13109        editor.handle_input("(", window, cx);
13110        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13111    });
13112
13113    // Mock response with 3 signatures
13114    let mocked_response = lsp::SignatureHelp {
13115        signatures: vec![
13116            lsp::SignatureInformation {
13117                label: "fn overloaded(x: i32)".to_string(),
13118                documentation: None,
13119                parameters: Some(vec![lsp::ParameterInformation {
13120                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13121                    documentation: None,
13122                }]),
13123                active_parameter: None,
13124            },
13125            lsp::SignatureInformation {
13126                label: "fn overloaded(x: i32, y: i32)".to_string(),
13127                documentation: None,
13128                parameters: Some(vec![
13129                    lsp::ParameterInformation {
13130                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13131                        documentation: None,
13132                    },
13133                    lsp::ParameterInformation {
13134                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13135                        documentation: None,
13136                    },
13137                ]),
13138                active_parameter: None,
13139            },
13140            lsp::SignatureInformation {
13141                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13142                documentation: None,
13143                parameters: Some(vec![
13144                    lsp::ParameterInformation {
13145                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13146                        documentation: None,
13147                    },
13148                    lsp::ParameterInformation {
13149                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13150                        documentation: None,
13151                    },
13152                    lsp::ParameterInformation {
13153                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13154                        documentation: None,
13155                    },
13156                ]),
13157                active_parameter: None,
13158            },
13159        ],
13160        active_signature: Some(1),
13161        active_parameter: Some(0),
13162    };
13163    handle_signature_help_request(&mut cx, mocked_response).await;
13164
13165    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13166        .await;
13167
13168    // Verify we have multiple signatures and the right one is selected
13169    cx.editor(|editor, _, _| {
13170        let popover = editor.signature_help_state.popover().cloned().unwrap();
13171        assert_eq!(popover.signatures.len(), 3);
13172        // active_signature was 1, so that should be the current
13173        assert_eq!(popover.current_signature, 1);
13174        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13175        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13176        assert_eq!(
13177            popover.signatures[2].label,
13178            "fn overloaded(x: i32, y: i32, z: i32)"
13179        );
13180    });
13181
13182    // Test navigation functionality
13183    cx.update_editor(|editor, window, cx| {
13184        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13185    });
13186
13187    cx.editor(|editor, _, _| {
13188        let popover = editor.signature_help_state.popover().cloned().unwrap();
13189        assert_eq!(popover.current_signature, 2);
13190    });
13191
13192    // Test wrap around
13193    cx.update_editor(|editor, window, cx| {
13194        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13195    });
13196
13197    cx.editor(|editor, _, _| {
13198        let popover = editor.signature_help_state.popover().cloned().unwrap();
13199        assert_eq!(popover.current_signature, 0);
13200    });
13201
13202    // Test previous navigation
13203    cx.update_editor(|editor, window, cx| {
13204        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13205    });
13206
13207    cx.editor(|editor, _, _| {
13208        let popover = editor.signature_help_state.popover().cloned().unwrap();
13209        assert_eq!(popover.current_signature, 2);
13210    });
13211}
13212
13213#[gpui::test]
13214async fn test_completion_mode(cx: &mut TestAppContext) {
13215    init_test(cx, |_| {});
13216    let mut cx = EditorLspTestContext::new_rust(
13217        lsp::ServerCapabilities {
13218            completion_provider: Some(lsp::CompletionOptions {
13219                resolve_provider: Some(true),
13220                ..Default::default()
13221            }),
13222            ..Default::default()
13223        },
13224        cx,
13225    )
13226    .await;
13227
13228    struct Run {
13229        run_description: &'static str,
13230        initial_state: String,
13231        buffer_marked_text: String,
13232        completion_label: &'static str,
13233        completion_text: &'static str,
13234        expected_with_insert_mode: String,
13235        expected_with_replace_mode: String,
13236        expected_with_replace_subsequence_mode: String,
13237        expected_with_replace_suffix_mode: String,
13238    }
13239
13240    let runs = [
13241        Run {
13242            run_description: "Start of word matches completion text",
13243            initial_state: "before ediˇ after".into(),
13244            buffer_marked_text: "before <edi|> after".into(),
13245            completion_label: "editor",
13246            completion_text: "editor",
13247            expected_with_insert_mode: "before editorˇ after".into(),
13248            expected_with_replace_mode: "before editorˇ after".into(),
13249            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13250            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13251        },
13252        Run {
13253            run_description: "Accept same text at the middle of the word",
13254            initial_state: "before ediˇtor after".into(),
13255            buffer_marked_text: "before <edi|tor> after".into(),
13256            completion_label: "editor",
13257            completion_text: "editor",
13258            expected_with_insert_mode: "before editorˇtor after".into(),
13259            expected_with_replace_mode: "before editorˇ after".into(),
13260            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13261            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13262        },
13263        Run {
13264            run_description: "End of word matches completion text -- cursor at end",
13265            initial_state: "before torˇ after".into(),
13266            buffer_marked_text: "before <tor|> after".into(),
13267            completion_label: "editor",
13268            completion_text: "editor",
13269            expected_with_insert_mode: "before editorˇ after".into(),
13270            expected_with_replace_mode: "before editorˇ after".into(),
13271            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13272            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13273        },
13274        Run {
13275            run_description: "End of word matches completion text -- cursor at start",
13276            initial_state: "before ˇtor after".into(),
13277            buffer_marked_text: "before <|tor> after".into(),
13278            completion_label: "editor",
13279            completion_text: "editor",
13280            expected_with_insert_mode: "before editorˇtor after".into(),
13281            expected_with_replace_mode: "before editorˇ after".into(),
13282            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13283            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13284        },
13285        Run {
13286            run_description: "Prepend text containing whitespace",
13287            initial_state: "pˇfield: bool".into(),
13288            buffer_marked_text: "<p|field>: bool".into(),
13289            completion_label: "pub ",
13290            completion_text: "pub ",
13291            expected_with_insert_mode: "pub ˇfield: bool".into(),
13292            expected_with_replace_mode: "pub ˇ: bool".into(),
13293            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13294            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13295        },
13296        Run {
13297            run_description: "Add element to start of list",
13298            initial_state: "[element_ˇelement_2]".into(),
13299            buffer_marked_text: "[<element_|element_2>]".into(),
13300            completion_label: "element_1",
13301            completion_text: "element_1",
13302            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13303            expected_with_replace_mode: "[element_1ˇ]".into(),
13304            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13305            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13306        },
13307        Run {
13308            run_description: "Add element to start of list -- first and second elements are equal",
13309            initial_state: "[elˇelement]".into(),
13310            buffer_marked_text: "[<el|element>]".into(),
13311            completion_label: "element",
13312            completion_text: "element",
13313            expected_with_insert_mode: "[elementˇelement]".into(),
13314            expected_with_replace_mode: "[elementˇ]".into(),
13315            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13316            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13317        },
13318        Run {
13319            run_description: "Ends with matching suffix",
13320            initial_state: "SubˇError".into(),
13321            buffer_marked_text: "<Sub|Error>".into(),
13322            completion_label: "SubscriptionError",
13323            completion_text: "SubscriptionError",
13324            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13325            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13326            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13327            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13328        },
13329        Run {
13330            run_description: "Suffix is a subsequence -- contiguous",
13331            initial_state: "SubˇErr".into(),
13332            buffer_marked_text: "<Sub|Err>".into(),
13333            completion_label: "SubscriptionError",
13334            completion_text: "SubscriptionError",
13335            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13336            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13337            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13338            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13339        },
13340        Run {
13341            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13342            initial_state: "Suˇscrirr".into(),
13343            buffer_marked_text: "<Su|scrirr>".into(),
13344            completion_label: "SubscriptionError",
13345            completion_text: "SubscriptionError",
13346            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13347            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13348            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13349            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13350        },
13351        Run {
13352            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13353            initial_state: "foo(indˇix)".into(),
13354            buffer_marked_text: "foo(<ind|ix>)".into(),
13355            completion_label: "node_index",
13356            completion_text: "node_index",
13357            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13358            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13359            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13360            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13361        },
13362        Run {
13363            run_description: "Replace range ends before cursor - should extend to cursor",
13364            initial_state: "before editˇo after".into(),
13365            buffer_marked_text: "before <{ed}>it|o after".into(),
13366            completion_label: "editor",
13367            completion_text: "editor",
13368            expected_with_insert_mode: "before editorˇo after".into(),
13369            expected_with_replace_mode: "before editorˇo after".into(),
13370            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13371            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13372        },
13373        Run {
13374            run_description: "Uses label for suffix matching",
13375            initial_state: "before ediˇtor after".into(),
13376            buffer_marked_text: "before <edi|tor> after".into(),
13377            completion_label: "editor",
13378            completion_text: "editor()",
13379            expected_with_insert_mode: "before editor()ˇtor after".into(),
13380            expected_with_replace_mode: "before editor()ˇ after".into(),
13381            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13382            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13383        },
13384        Run {
13385            run_description: "Case insensitive subsequence and suffix matching",
13386            initial_state: "before EDiˇtoR after".into(),
13387            buffer_marked_text: "before <EDi|toR> after".into(),
13388            completion_label: "editor",
13389            completion_text: "editor",
13390            expected_with_insert_mode: "before editorˇtoR after".into(),
13391            expected_with_replace_mode: "before editorˇ after".into(),
13392            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13393            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13394        },
13395    ];
13396
13397    for run in runs {
13398        let run_variations = [
13399            (LspInsertMode::Insert, run.expected_with_insert_mode),
13400            (LspInsertMode::Replace, run.expected_with_replace_mode),
13401            (
13402                LspInsertMode::ReplaceSubsequence,
13403                run.expected_with_replace_subsequence_mode,
13404            ),
13405            (
13406                LspInsertMode::ReplaceSuffix,
13407                run.expected_with_replace_suffix_mode,
13408            ),
13409        ];
13410
13411        for (lsp_insert_mode, expected_text) in run_variations {
13412            eprintln!(
13413                "run = {:?}, mode = {lsp_insert_mode:.?}",
13414                run.run_description,
13415            );
13416
13417            update_test_language_settings(&mut cx, |settings| {
13418                settings.defaults.completions = Some(CompletionSettingsContent {
13419                    lsp_insert_mode: Some(lsp_insert_mode),
13420                    words: Some(WordsCompletionMode::Disabled),
13421                    words_min_length: Some(0),
13422                    ..Default::default()
13423                });
13424            });
13425
13426            cx.set_state(&run.initial_state);
13427            cx.update_editor(|editor, window, cx| {
13428                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13429            });
13430
13431            let counter = Arc::new(AtomicUsize::new(0));
13432            handle_completion_request_with_insert_and_replace(
13433                &mut cx,
13434                &run.buffer_marked_text,
13435                vec![(run.completion_label, run.completion_text)],
13436                counter.clone(),
13437            )
13438            .await;
13439            cx.condition(|editor, _| editor.context_menu_visible())
13440                .await;
13441            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13442
13443            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13444                editor
13445                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13446                    .unwrap()
13447            });
13448            cx.assert_editor_state(&expected_text);
13449            handle_resolve_completion_request(&mut cx, None).await;
13450            apply_additional_edits.await.unwrap();
13451        }
13452    }
13453}
13454
13455#[gpui::test]
13456async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13457    init_test(cx, |_| {});
13458    let mut cx = EditorLspTestContext::new_rust(
13459        lsp::ServerCapabilities {
13460            completion_provider: Some(lsp::CompletionOptions {
13461                resolve_provider: Some(true),
13462                ..Default::default()
13463            }),
13464            ..Default::default()
13465        },
13466        cx,
13467    )
13468    .await;
13469
13470    let initial_state = "SubˇError";
13471    let buffer_marked_text = "<Sub|Error>";
13472    let completion_text = "SubscriptionError";
13473    let expected_with_insert_mode = "SubscriptionErrorˇError";
13474    let expected_with_replace_mode = "SubscriptionErrorˇ";
13475
13476    update_test_language_settings(&mut cx, |settings| {
13477        settings.defaults.completions = Some(CompletionSettingsContent {
13478            words: Some(WordsCompletionMode::Disabled),
13479            words_min_length: Some(0),
13480            // set the opposite here to ensure that the action is overriding the default behavior
13481            lsp_insert_mode: Some(LspInsertMode::Insert),
13482            ..Default::default()
13483        });
13484    });
13485
13486    cx.set_state(initial_state);
13487    cx.update_editor(|editor, window, cx| {
13488        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13489    });
13490
13491    let counter = Arc::new(AtomicUsize::new(0));
13492    handle_completion_request_with_insert_and_replace(
13493        &mut cx,
13494        buffer_marked_text,
13495        vec![(completion_text, completion_text)],
13496        counter.clone(),
13497    )
13498    .await;
13499    cx.condition(|editor, _| editor.context_menu_visible())
13500        .await;
13501    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13502
13503    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13504        editor
13505            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13506            .unwrap()
13507    });
13508    cx.assert_editor_state(expected_with_replace_mode);
13509    handle_resolve_completion_request(&mut cx, None).await;
13510    apply_additional_edits.await.unwrap();
13511
13512    update_test_language_settings(&mut cx, |settings| {
13513        settings.defaults.completions = Some(CompletionSettingsContent {
13514            words: Some(WordsCompletionMode::Disabled),
13515            words_min_length: Some(0),
13516            // set the opposite here to ensure that the action is overriding the default behavior
13517            lsp_insert_mode: Some(LspInsertMode::Replace),
13518            ..Default::default()
13519        });
13520    });
13521
13522    cx.set_state(initial_state);
13523    cx.update_editor(|editor, window, cx| {
13524        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13525    });
13526    handle_completion_request_with_insert_and_replace(
13527        &mut cx,
13528        buffer_marked_text,
13529        vec![(completion_text, completion_text)],
13530        counter.clone(),
13531    )
13532    .await;
13533    cx.condition(|editor, _| editor.context_menu_visible())
13534        .await;
13535    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13536
13537    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13538        editor
13539            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13540            .unwrap()
13541    });
13542    cx.assert_editor_state(expected_with_insert_mode);
13543    handle_resolve_completion_request(&mut cx, None).await;
13544    apply_additional_edits.await.unwrap();
13545}
13546
13547#[gpui::test]
13548async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
13549    init_test(cx, |_| {});
13550    let mut cx = EditorLspTestContext::new_rust(
13551        lsp::ServerCapabilities {
13552            completion_provider: Some(lsp::CompletionOptions {
13553                resolve_provider: Some(true),
13554                ..Default::default()
13555            }),
13556            ..Default::default()
13557        },
13558        cx,
13559    )
13560    .await;
13561
13562    // scenario: surrounding text matches completion text
13563    let completion_text = "to_offset";
13564    let initial_state = indoc! {"
13565        1. buf.to_offˇsuffix
13566        2. buf.to_offˇsuf
13567        3. buf.to_offˇfix
13568        4. buf.to_offˇ
13569        5. into_offˇensive
13570        6. ˇsuffix
13571        7. let ˇ //
13572        8. aaˇzz
13573        9. buf.to_off«zzzzzˇ»suffix
13574        10. buf.«ˇzzzzz»suffix
13575        11. to_off«ˇzzzzz»
13576
13577        buf.to_offˇsuffix  // newest cursor
13578    "};
13579    let completion_marked_buffer = indoc! {"
13580        1. buf.to_offsuffix
13581        2. buf.to_offsuf
13582        3. buf.to_offfix
13583        4. buf.to_off
13584        5. into_offensive
13585        6. suffix
13586        7. let  //
13587        8. aazz
13588        9. buf.to_offzzzzzsuffix
13589        10. buf.zzzzzsuffix
13590        11. to_offzzzzz
13591
13592        buf.<to_off|suffix>  // newest cursor
13593    "};
13594    let expected = indoc! {"
13595        1. buf.to_offsetˇ
13596        2. buf.to_offsetˇsuf
13597        3. buf.to_offsetˇfix
13598        4. buf.to_offsetˇ
13599        5. into_offsetˇensive
13600        6. to_offsetˇsuffix
13601        7. let to_offsetˇ //
13602        8. aato_offsetˇzz
13603        9. buf.to_offsetˇ
13604        10. buf.to_offsetˇsuffix
13605        11. to_offsetˇ
13606
13607        buf.to_offsetˇ  // newest cursor
13608    "};
13609    cx.set_state(initial_state);
13610    cx.update_editor(|editor, window, cx| {
13611        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13612    });
13613    handle_completion_request_with_insert_and_replace(
13614        &mut cx,
13615        completion_marked_buffer,
13616        vec![(completion_text, completion_text)],
13617        Arc::new(AtomicUsize::new(0)),
13618    )
13619    .await;
13620    cx.condition(|editor, _| editor.context_menu_visible())
13621        .await;
13622    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13623        editor
13624            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13625            .unwrap()
13626    });
13627    cx.assert_editor_state(expected);
13628    handle_resolve_completion_request(&mut cx, None).await;
13629    apply_additional_edits.await.unwrap();
13630
13631    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
13632    let completion_text = "foo_and_bar";
13633    let initial_state = indoc! {"
13634        1. ooanbˇ
13635        2. zooanbˇ
13636        3. ooanbˇz
13637        4. zooanbˇz
13638        5. ooanˇ
13639        6. oanbˇ
13640
13641        ooanbˇ
13642    "};
13643    let completion_marked_buffer = indoc! {"
13644        1. ooanb
13645        2. zooanb
13646        3. ooanbz
13647        4. zooanbz
13648        5. ooan
13649        6. oanb
13650
13651        <ooanb|>
13652    "};
13653    let expected = indoc! {"
13654        1. foo_and_barˇ
13655        2. zfoo_and_barˇ
13656        3. foo_and_barˇz
13657        4. zfoo_and_barˇz
13658        5. ooanfoo_and_barˇ
13659        6. oanbfoo_and_barˇ
13660
13661        foo_and_barˇ
13662    "};
13663    cx.set_state(initial_state);
13664    cx.update_editor(|editor, window, cx| {
13665        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13666    });
13667    handle_completion_request_with_insert_and_replace(
13668        &mut cx,
13669        completion_marked_buffer,
13670        vec![(completion_text, completion_text)],
13671        Arc::new(AtomicUsize::new(0)),
13672    )
13673    .await;
13674    cx.condition(|editor, _| editor.context_menu_visible())
13675        .await;
13676    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13677        editor
13678            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13679            .unwrap()
13680    });
13681    cx.assert_editor_state(expected);
13682    handle_resolve_completion_request(&mut cx, None).await;
13683    apply_additional_edits.await.unwrap();
13684
13685    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
13686    // (expects the same as if it was inserted at the end)
13687    let completion_text = "foo_and_bar";
13688    let initial_state = indoc! {"
13689        1. ooˇanb
13690        2. zooˇanb
13691        3. ooˇanbz
13692        4. zooˇanbz
13693
13694        ooˇanb
13695    "};
13696    let completion_marked_buffer = indoc! {"
13697        1. ooanb
13698        2. zooanb
13699        3. ooanbz
13700        4. zooanbz
13701
13702        <oo|anb>
13703    "};
13704    let expected = indoc! {"
13705        1. foo_and_barˇ
13706        2. zfoo_and_barˇ
13707        3. foo_and_barˇz
13708        4. zfoo_and_barˇz
13709
13710        foo_and_barˇ
13711    "};
13712    cx.set_state(initial_state);
13713    cx.update_editor(|editor, window, cx| {
13714        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13715    });
13716    handle_completion_request_with_insert_and_replace(
13717        &mut cx,
13718        completion_marked_buffer,
13719        vec![(completion_text, completion_text)],
13720        Arc::new(AtomicUsize::new(0)),
13721    )
13722    .await;
13723    cx.condition(|editor, _| editor.context_menu_visible())
13724        .await;
13725    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13726        editor
13727            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13728            .unwrap()
13729    });
13730    cx.assert_editor_state(expected);
13731    handle_resolve_completion_request(&mut cx, None).await;
13732    apply_additional_edits.await.unwrap();
13733}
13734
13735// This used to crash
13736#[gpui::test]
13737async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
13738    init_test(cx, |_| {});
13739
13740    let buffer_text = indoc! {"
13741        fn main() {
13742            10.satu;
13743
13744            //
13745            // separate cursors so they open in different excerpts (manually reproducible)
13746            //
13747
13748            10.satu20;
13749        }
13750    "};
13751    let multibuffer_text_with_selections = indoc! {"
13752        fn main() {
13753            10.satuˇ;
13754
13755            //
13756
13757            //
13758
13759            10.satuˇ20;
13760        }
13761    "};
13762    let expected_multibuffer = indoc! {"
13763        fn main() {
13764            10.saturating_sub()ˇ;
13765
13766            //
13767
13768            //
13769
13770            10.saturating_sub()ˇ;
13771        }
13772    "};
13773
13774    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
13775    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
13776
13777    let fs = FakeFs::new(cx.executor());
13778    fs.insert_tree(
13779        path!("/a"),
13780        json!({
13781            "main.rs": buffer_text,
13782        }),
13783    )
13784    .await;
13785
13786    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13787    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13788    language_registry.add(rust_lang());
13789    let mut fake_servers = language_registry.register_fake_lsp(
13790        "Rust",
13791        FakeLspAdapter {
13792            capabilities: lsp::ServerCapabilities {
13793                completion_provider: Some(lsp::CompletionOptions {
13794                    resolve_provider: None,
13795                    ..lsp::CompletionOptions::default()
13796                }),
13797                ..lsp::ServerCapabilities::default()
13798            },
13799            ..FakeLspAdapter::default()
13800        },
13801    );
13802    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13803    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13804    let buffer = project
13805        .update(cx, |project, cx| {
13806            project.open_local_buffer(path!("/a/main.rs"), cx)
13807        })
13808        .await
13809        .unwrap();
13810
13811    let multi_buffer = cx.new(|cx| {
13812        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
13813        multi_buffer.push_excerpts(
13814            buffer.clone(),
13815            [ExcerptRange::new(0..first_excerpt_end)],
13816            cx,
13817        );
13818        multi_buffer.push_excerpts(
13819            buffer.clone(),
13820            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
13821            cx,
13822        );
13823        multi_buffer
13824    });
13825
13826    let editor = workspace
13827        .update(cx, |_, window, cx| {
13828            cx.new(|cx| {
13829                Editor::new(
13830                    EditorMode::Full {
13831                        scale_ui_elements_with_buffer_font_size: false,
13832                        show_active_line_background: false,
13833                        sized_by_content: false,
13834                    },
13835                    multi_buffer.clone(),
13836                    Some(project.clone()),
13837                    window,
13838                    cx,
13839                )
13840            })
13841        })
13842        .unwrap();
13843
13844    let pane = workspace
13845        .update(cx, |workspace, _, _| workspace.active_pane().clone())
13846        .unwrap();
13847    pane.update_in(cx, |pane, window, cx| {
13848        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
13849    });
13850
13851    let fake_server = fake_servers.next().await.unwrap();
13852
13853    editor.update_in(cx, |editor, window, cx| {
13854        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13855            s.select_ranges([
13856                Point::new(1, 11)..Point::new(1, 11),
13857                Point::new(7, 11)..Point::new(7, 11),
13858            ])
13859        });
13860
13861        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
13862    });
13863
13864    editor.update_in(cx, |editor, window, cx| {
13865        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13866    });
13867
13868    fake_server
13869        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13870            let completion_item = lsp::CompletionItem {
13871                label: "saturating_sub()".into(),
13872                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
13873                    lsp::InsertReplaceEdit {
13874                        new_text: "saturating_sub()".to_owned(),
13875                        insert: lsp::Range::new(
13876                            lsp::Position::new(7, 7),
13877                            lsp::Position::new(7, 11),
13878                        ),
13879                        replace: lsp::Range::new(
13880                            lsp::Position::new(7, 7),
13881                            lsp::Position::new(7, 13),
13882                        ),
13883                    },
13884                )),
13885                ..lsp::CompletionItem::default()
13886            };
13887
13888            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
13889        })
13890        .next()
13891        .await
13892        .unwrap();
13893
13894    cx.condition(&editor, |editor, _| editor.context_menu_visible())
13895        .await;
13896
13897    editor
13898        .update_in(cx, |editor, window, cx| {
13899            editor
13900                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13901                .unwrap()
13902        })
13903        .await
13904        .unwrap();
13905
13906    editor.update(cx, |editor, cx| {
13907        assert_text_with_selections(editor, expected_multibuffer, cx);
13908    })
13909}
13910
13911#[gpui::test]
13912async fn test_completion(cx: &mut TestAppContext) {
13913    init_test(cx, |_| {});
13914
13915    let mut cx = EditorLspTestContext::new_rust(
13916        lsp::ServerCapabilities {
13917            completion_provider: Some(lsp::CompletionOptions {
13918                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13919                resolve_provider: Some(true),
13920                ..Default::default()
13921            }),
13922            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13923            ..Default::default()
13924        },
13925        cx,
13926    )
13927    .await;
13928    let counter = Arc::new(AtomicUsize::new(0));
13929
13930    cx.set_state(indoc! {"
13931        oneˇ
13932        two
13933        three
13934    "});
13935    cx.simulate_keystroke(".");
13936    handle_completion_request(
13937        indoc! {"
13938            one.|<>
13939            two
13940            three
13941        "},
13942        vec!["first_completion", "second_completion"],
13943        true,
13944        counter.clone(),
13945        &mut cx,
13946    )
13947    .await;
13948    cx.condition(|editor, _| editor.context_menu_visible())
13949        .await;
13950    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13951
13952    let _handler = handle_signature_help_request(
13953        &mut cx,
13954        lsp::SignatureHelp {
13955            signatures: vec![lsp::SignatureInformation {
13956                label: "test signature".to_string(),
13957                documentation: None,
13958                parameters: Some(vec![lsp::ParameterInformation {
13959                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
13960                    documentation: None,
13961                }]),
13962                active_parameter: None,
13963            }],
13964            active_signature: None,
13965            active_parameter: None,
13966        },
13967    );
13968    cx.update_editor(|editor, window, cx| {
13969        assert!(
13970            !editor.signature_help_state.is_shown(),
13971            "No signature help was called for"
13972        );
13973        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13974    });
13975    cx.run_until_parked();
13976    cx.update_editor(|editor, _, _| {
13977        assert!(
13978            !editor.signature_help_state.is_shown(),
13979            "No signature help should be shown when completions menu is open"
13980        );
13981    });
13982
13983    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13984        editor.context_menu_next(&Default::default(), window, cx);
13985        editor
13986            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13987            .unwrap()
13988    });
13989    cx.assert_editor_state(indoc! {"
13990        one.second_completionˇ
13991        two
13992        three
13993    "});
13994
13995    handle_resolve_completion_request(
13996        &mut cx,
13997        Some(vec![
13998            (
13999                //This overlaps with the primary completion edit which is
14000                //misbehavior from the LSP spec, test that we filter it out
14001                indoc! {"
14002                    one.second_ˇcompletion
14003                    two
14004                    threeˇ
14005                "},
14006                "overlapping additional edit",
14007            ),
14008            (
14009                indoc! {"
14010                    one.second_completion
14011                    two
14012                    threeˇ
14013                "},
14014                "\nadditional edit",
14015            ),
14016        ]),
14017    )
14018    .await;
14019    apply_additional_edits.await.unwrap();
14020    cx.assert_editor_state(indoc! {"
14021        one.second_completionˇ
14022        two
14023        three
14024        additional edit
14025    "});
14026
14027    cx.set_state(indoc! {"
14028        one.second_completion
14029        twoˇ
14030        threeˇ
14031        additional edit
14032    "});
14033    cx.simulate_keystroke(" ");
14034    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14035    cx.simulate_keystroke("s");
14036    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14037
14038    cx.assert_editor_state(indoc! {"
14039        one.second_completion
14040        two sˇ
14041        three sˇ
14042        additional edit
14043    "});
14044    handle_completion_request(
14045        indoc! {"
14046            one.second_completion
14047            two s
14048            three <s|>
14049            additional edit
14050        "},
14051        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14052        true,
14053        counter.clone(),
14054        &mut cx,
14055    )
14056    .await;
14057    cx.condition(|editor, _| editor.context_menu_visible())
14058        .await;
14059    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14060
14061    cx.simulate_keystroke("i");
14062
14063    handle_completion_request(
14064        indoc! {"
14065            one.second_completion
14066            two si
14067            three <si|>
14068            additional edit
14069        "},
14070        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14071        true,
14072        counter.clone(),
14073        &mut cx,
14074    )
14075    .await;
14076    cx.condition(|editor, _| editor.context_menu_visible())
14077        .await;
14078    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14079
14080    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14081        editor
14082            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14083            .unwrap()
14084    });
14085    cx.assert_editor_state(indoc! {"
14086        one.second_completion
14087        two sixth_completionˇ
14088        three sixth_completionˇ
14089        additional edit
14090    "});
14091
14092    apply_additional_edits.await.unwrap();
14093
14094    update_test_language_settings(&mut cx, |settings| {
14095        settings.defaults.show_completions_on_input = Some(false);
14096    });
14097    cx.set_state("editorˇ");
14098    cx.simulate_keystroke(".");
14099    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14100    cx.simulate_keystrokes("c l o");
14101    cx.assert_editor_state("editor.cloˇ");
14102    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14103    cx.update_editor(|editor, window, cx| {
14104        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14105    });
14106    handle_completion_request(
14107        "editor.<clo|>",
14108        vec!["close", "clobber"],
14109        true,
14110        counter.clone(),
14111        &mut cx,
14112    )
14113    .await;
14114    cx.condition(|editor, _| editor.context_menu_visible())
14115        .await;
14116    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14117
14118    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14119        editor
14120            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14121            .unwrap()
14122    });
14123    cx.assert_editor_state("editor.clobberˇ");
14124    handle_resolve_completion_request(&mut cx, None).await;
14125    apply_additional_edits.await.unwrap();
14126}
14127
14128#[gpui::test]
14129async fn test_completion_reuse(cx: &mut TestAppContext) {
14130    init_test(cx, |_| {});
14131
14132    let mut cx = EditorLspTestContext::new_rust(
14133        lsp::ServerCapabilities {
14134            completion_provider: Some(lsp::CompletionOptions {
14135                trigger_characters: Some(vec![".".to_string()]),
14136                ..Default::default()
14137            }),
14138            ..Default::default()
14139        },
14140        cx,
14141    )
14142    .await;
14143
14144    let counter = Arc::new(AtomicUsize::new(0));
14145    cx.set_state("objˇ");
14146    cx.simulate_keystroke(".");
14147
14148    // Initial completion request returns complete results
14149    let is_incomplete = false;
14150    handle_completion_request(
14151        "obj.|<>",
14152        vec!["a", "ab", "abc"],
14153        is_incomplete,
14154        counter.clone(),
14155        &mut cx,
14156    )
14157    .await;
14158    cx.run_until_parked();
14159    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14160    cx.assert_editor_state("obj.ˇ");
14161    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14162
14163    // Type "a" - filters existing completions
14164    cx.simulate_keystroke("a");
14165    cx.run_until_parked();
14166    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14167    cx.assert_editor_state("obj.aˇ");
14168    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14169
14170    // Type "b" - filters existing completions
14171    cx.simulate_keystroke("b");
14172    cx.run_until_parked();
14173    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14174    cx.assert_editor_state("obj.abˇ");
14175    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14176
14177    // Type "c" - filters existing completions
14178    cx.simulate_keystroke("c");
14179    cx.run_until_parked();
14180    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14181    cx.assert_editor_state("obj.abcˇ");
14182    check_displayed_completions(vec!["abc"], &mut cx);
14183
14184    // Backspace to delete "c" - filters existing completions
14185    cx.update_editor(|editor, window, cx| {
14186        editor.backspace(&Backspace, window, cx);
14187    });
14188    cx.run_until_parked();
14189    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14190    cx.assert_editor_state("obj.abˇ");
14191    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14192
14193    // Moving cursor to the left dismisses menu.
14194    cx.update_editor(|editor, window, cx| {
14195        editor.move_left(&MoveLeft, window, cx);
14196    });
14197    cx.run_until_parked();
14198    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14199    cx.assert_editor_state("obj.aˇb");
14200    cx.update_editor(|editor, _, _| {
14201        assert_eq!(editor.context_menu_visible(), false);
14202    });
14203
14204    // Type "b" - new request
14205    cx.simulate_keystroke("b");
14206    let is_incomplete = false;
14207    handle_completion_request(
14208        "obj.<ab|>a",
14209        vec!["ab", "abc"],
14210        is_incomplete,
14211        counter.clone(),
14212        &mut cx,
14213    )
14214    .await;
14215    cx.run_until_parked();
14216    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14217    cx.assert_editor_state("obj.abˇb");
14218    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14219
14220    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14221    cx.update_editor(|editor, window, cx| {
14222        editor.backspace(&Backspace, window, cx);
14223    });
14224    let is_incomplete = false;
14225    handle_completion_request(
14226        "obj.<a|>b",
14227        vec!["a", "ab", "abc"],
14228        is_incomplete,
14229        counter.clone(),
14230        &mut cx,
14231    )
14232    .await;
14233    cx.run_until_parked();
14234    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14235    cx.assert_editor_state("obj.aˇb");
14236    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14237
14238    // Backspace to delete "a" - dismisses menu.
14239    cx.update_editor(|editor, window, cx| {
14240        editor.backspace(&Backspace, window, cx);
14241    });
14242    cx.run_until_parked();
14243    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14244    cx.assert_editor_state("obj.ˇb");
14245    cx.update_editor(|editor, _, _| {
14246        assert_eq!(editor.context_menu_visible(), false);
14247    });
14248}
14249
14250#[gpui::test]
14251async fn test_word_completion(cx: &mut TestAppContext) {
14252    let lsp_fetch_timeout_ms = 10;
14253    init_test(cx, |language_settings| {
14254        language_settings.defaults.completions = Some(CompletionSettingsContent {
14255            words_min_length: Some(0),
14256            lsp_fetch_timeout_ms: Some(10),
14257            lsp_insert_mode: Some(LspInsertMode::Insert),
14258            ..Default::default()
14259        });
14260    });
14261
14262    let mut cx = EditorLspTestContext::new_rust(
14263        lsp::ServerCapabilities {
14264            completion_provider: Some(lsp::CompletionOptions {
14265                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14266                ..lsp::CompletionOptions::default()
14267            }),
14268            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14269            ..lsp::ServerCapabilities::default()
14270        },
14271        cx,
14272    )
14273    .await;
14274
14275    let throttle_completions = Arc::new(AtomicBool::new(false));
14276
14277    let lsp_throttle_completions = throttle_completions.clone();
14278    let _completion_requests_handler =
14279        cx.lsp
14280            .server
14281            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14282                let lsp_throttle_completions = lsp_throttle_completions.clone();
14283                let cx = cx.clone();
14284                async move {
14285                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14286                        cx.background_executor()
14287                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14288                            .await;
14289                    }
14290                    Ok(Some(lsp::CompletionResponse::Array(vec![
14291                        lsp::CompletionItem {
14292                            label: "first".into(),
14293                            ..lsp::CompletionItem::default()
14294                        },
14295                        lsp::CompletionItem {
14296                            label: "last".into(),
14297                            ..lsp::CompletionItem::default()
14298                        },
14299                    ])))
14300                }
14301            });
14302
14303    cx.set_state(indoc! {"
14304        oneˇ
14305        two
14306        three
14307    "});
14308    cx.simulate_keystroke(".");
14309    cx.executor().run_until_parked();
14310    cx.condition(|editor, _| editor.context_menu_visible())
14311        .await;
14312    cx.update_editor(|editor, window, cx| {
14313        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14314        {
14315            assert_eq!(
14316                completion_menu_entries(menu),
14317                &["first", "last"],
14318                "When LSP server is fast to reply, no fallback word completions are used"
14319            );
14320        } else {
14321            panic!("expected completion menu to be open");
14322        }
14323        editor.cancel(&Cancel, window, cx);
14324    });
14325    cx.executor().run_until_parked();
14326    cx.condition(|editor, _| !editor.context_menu_visible())
14327        .await;
14328
14329    throttle_completions.store(true, atomic::Ordering::Release);
14330    cx.simulate_keystroke(".");
14331    cx.executor()
14332        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14333    cx.executor().run_until_parked();
14334    cx.condition(|editor, _| editor.context_menu_visible())
14335        .await;
14336    cx.update_editor(|editor, _, _| {
14337        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14338        {
14339            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14340                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14341        } else {
14342            panic!("expected completion menu to be open");
14343        }
14344    });
14345}
14346
14347#[gpui::test]
14348async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14349    init_test(cx, |language_settings| {
14350        language_settings.defaults.completions = Some(CompletionSettingsContent {
14351            words: Some(WordsCompletionMode::Enabled),
14352            words_min_length: Some(0),
14353            lsp_insert_mode: Some(LspInsertMode::Insert),
14354            ..Default::default()
14355        });
14356    });
14357
14358    let mut cx = EditorLspTestContext::new_rust(
14359        lsp::ServerCapabilities {
14360            completion_provider: Some(lsp::CompletionOptions {
14361                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14362                ..lsp::CompletionOptions::default()
14363            }),
14364            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14365            ..lsp::ServerCapabilities::default()
14366        },
14367        cx,
14368    )
14369    .await;
14370
14371    let _completion_requests_handler =
14372        cx.lsp
14373            .server
14374            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14375                Ok(Some(lsp::CompletionResponse::Array(vec![
14376                    lsp::CompletionItem {
14377                        label: "first".into(),
14378                        ..lsp::CompletionItem::default()
14379                    },
14380                    lsp::CompletionItem {
14381                        label: "last".into(),
14382                        ..lsp::CompletionItem::default()
14383                    },
14384                ])))
14385            });
14386
14387    cx.set_state(indoc! {"ˇ
14388        first
14389        last
14390        second
14391    "});
14392    cx.simulate_keystroke(".");
14393    cx.executor().run_until_parked();
14394    cx.condition(|editor, _| editor.context_menu_visible())
14395        .await;
14396    cx.update_editor(|editor, _, _| {
14397        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14398        {
14399            assert_eq!(
14400                completion_menu_entries(menu),
14401                &["first", "last", "second"],
14402                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14403            );
14404        } else {
14405            panic!("expected completion menu to be open");
14406        }
14407    });
14408}
14409
14410#[gpui::test]
14411async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14412    init_test(cx, |language_settings| {
14413        language_settings.defaults.completions = Some(CompletionSettingsContent {
14414            words: Some(WordsCompletionMode::Disabled),
14415            words_min_length: Some(0),
14416            lsp_insert_mode: Some(LspInsertMode::Insert),
14417            ..Default::default()
14418        });
14419    });
14420
14421    let mut cx = EditorLspTestContext::new_rust(
14422        lsp::ServerCapabilities {
14423            completion_provider: Some(lsp::CompletionOptions {
14424                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14425                ..lsp::CompletionOptions::default()
14426            }),
14427            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14428            ..lsp::ServerCapabilities::default()
14429        },
14430        cx,
14431    )
14432    .await;
14433
14434    let _completion_requests_handler =
14435        cx.lsp
14436            .server
14437            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14438                panic!("LSP completions should not be queried when dealing with word completions")
14439            });
14440
14441    cx.set_state(indoc! {"ˇ
14442        first
14443        last
14444        second
14445    "});
14446    cx.update_editor(|editor, window, cx| {
14447        editor.show_word_completions(&ShowWordCompletions, window, cx);
14448    });
14449    cx.executor().run_until_parked();
14450    cx.condition(|editor, _| editor.context_menu_visible())
14451        .await;
14452    cx.update_editor(|editor, _, _| {
14453        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14454        {
14455            assert_eq!(
14456                completion_menu_entries(menu),
14457                &["first", "last", "second"],
14458                "`ShowWordCompletions` action should show word completions"
14459            );
14460        } else {
14461            panic!("expected completion menu to be open");
14462        }
14463    });
14464
14465    cx.simulate_keystroke("l");
14466    cx.executor().run_until_parked();
14467    cx.condition(|editor, _| editor.context_menu_visible())
14468        .await;
14469    cx.update_editor(|editor, _, _| {
14470        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14471        {
14472            assert_eq!(
14473                completion_menu_entries(menu),
14474                &["last"],
14475                "After showing word completions, further editing should filter them and not query the LSP"
14476            );
14477        } else {
14478            panic!("expected completion menu to be open");
14479        }
14480    });
14481}
14482
14483#[gpui::test]
14484async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14485    init_test(cx, |language_settings| {
14486        language_settings.defaults.completions = Some(CompletionSettingsContent {
14487            words_min_length: Some(0),
14488            lsp: Some(false),
14489            lsp_insert_mode: Some(LspInsertMode::Insert),
14490            ..Default::default()
14491        });
14492    });
14493
14494    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14495
14496    cx.set_state(indoc! {"ˇ
14497        0_usize
14498        let
14499        33
14500        4.5f32
14501    "});
14502    cx.update_editor(|editor, window, cx| {
14503        editor.show_completions(&ShowCompletions::default(), window, cx);
14504    });
14505    cx.executor().run_until_parked();
14506    cx.condition(|editor, _| editor.context_menu_visible())
14507        .await;
14508    cx.update_editor(|editor, window, cx| {
14509        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14510        {
14511            assert_eq!(
14512                completion_menu_entries(menu),
14513                &["let"],
14514                "With no digits in the completion query, no digits should be in the word completions"
14515            );
14516        } else {
14517            panic!("expected completion menu to be open");
14518        }
14519        editor.cancel(&Cancel, window, cx);
14520    });
14521
14522    cx.set_state(indoc! {"14523        0_usize
14524        let
14525        3
14526        33.35f32
14527    "});
14528    cx.update_editor(|editor, window, cx| {
14529        editor.show_completions(&ShowCompletions::default(), window, cx);
14530    });
14531    cx.executor().run_until_parked();
14532    cx.condition(|editor, _| editor.context_menu_visible())
14533        .await;
14534    cx.update_editor(|editor, _, _| {
14535        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14536        {
14537            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14538                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14539        } else {
14540            panic!("expected completion menu to be open");
14541        }
14542    });
14543}
14544
14545#[gpui::test]
14546async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
14547    init_test(cx, |language_settings| {
14548        language_settings.defaults.completions = Some(CompletionSettingsContent {
14549            words: Some(WordsCompletionMode::Enabled),
14550            words_min_length: Some(3),
14551            lsp_insert_mode: Some(LspInsertMode::Insert),
14552            ..Default::default()
14553        });
14554    });
14555
14556    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14557    cx.set_state(indoc! {"ˇ
14558        wow
14559        wowen
14560        wowser
14561    "});
14562    cx.simulate_keystroke("w");
14563    cx.executor().run_until_parked();
14564    cx.update_editor(|editor, _, _| {
14565        if editor.context_menu.borrow_mut().is_some() {
14566            panic!(
14567                "expected completion menu to be hidden, as words completion threshold is not met"
14568            );
14569        }
14570    });
14571
14572    cx.update_editor(|editor, window, cx| {
14573        editor.show_word_completions(&ShowWordCompletions, window, cx);
14574    });
14575    cx.executor().run_until_parked();
14576    cx.update_editor(|editor, window, cx| {
14577        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14578        {
14579            assert_eq!(completion_menu_entries(menu), &["wowser", "wowen", "wow"], "Even though the threshold is not met, invoking word completions with an action should provide the completions");
14580        } else {
14581            panic!("expected completion menu to be open after the word completions are called with an action");
14582        }
14583
14584        editor.cancel(&Cancel, window, cx);
14585    });
14586    cx.update_editor(|editor, _, _| {
14587        if editor.context_menu.borrow_mut().is_some() {
14588            panic!("expected completion menu to be hidden after canceling");
14589        }
14590    });
14591
14592    cx.simulate_keystroke("o");
14593    cx.executor().run_until_parked();
14594    cx.update_editor(|editor, _, _| {
14595        if editor.context_menu.borrow_mut().is_some() {
14596            panic!(
14597                "expected completion menu to be hidden, as words completion threshold is not met still"
14598            );
14599        }
14600    });
14601
14602    cx.simulate_keystroke("w");
14603    cx.executor().run_until_parked();
14604    cx.update_editor(|editor, _, _| {
14605        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14606        {
14607            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
14608        } else {
14609            panic!("expected completion menu to be open after the word completions threshold is met");
14610        }
14611    });
14612}
14613
14614#[gpui::test]
14615async fn test_word_completions_disabled(cx: &mut TestAppContext) {
14616    init_test(cx, |language_settings| {
14617        language_settings.defaults.completions = Some(CompletionSettingsContent {
14618            words: Some(WordsCompletionMode::Enabled),
14619            words_min_length: Some(0),
14620            lsp_insert_mode: Some(LspInsertMode::Insert),
14621            ..Default::default()
14622        });
14623    });
14624
14625    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14626    cx.update_editor(|editor, _, _| {
14627        editor.disable_word_completions();
14628    });
14629    cx.set_state(indoc! {"ˇ
14630        wow
14631        wowen
14632        wowser
14633    "});
14634    cx.simulate_keystroke("w");
14635    cx.executor().run_until_parked();
14636    cx.update_editor(|editor, _, _| {
14637        if editor.context_menu.borrow_mut().is_some() {
14638            panic!(
14639                "expected completion menu to be hidden, as words completion are disabled for this editor"
14640            );
14641        }
14642    });
14643
14644    cx.update_editor(|editor, window, cx| {
14645        editor.show_word_completions(&ShowWordCompletions, window, cx);
14646    });
14647    cx.executor().run_until_parked();
14648    cx.update_editor(|editor, _, _| {
14649        if editor.context_menu.borrow_mut().is_some() {
14650            panic!(
14651                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
14652            );
14653        }
14654    });
14655}
14656
14657fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
14658    let position = || lsp::Position {
14659        line: params.text_document_position.position.line,
14660        character: params.text_document_position.position.character,
14661    };
14662    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14663        range: lsp::Range {
14664            start: position(),
14665            end: position(),
14666        },
14667        new_text: text.to_string(),
14668    }))
14669}
14670
14671#[gpui::test]
14672async fn test_multiline_completion(cx: &mut TestAppContext) {
14673    init_test(cx, |_| {});
14674
14675    let fs = FakeFs::new(cx.executor());
14676    fs.insert_tree(
14677        path!("/a"),
14678        json!({
14679            "main.ts": "a",
14680        }),
14681    )
14682    .await;
14683
14684    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14685    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14686    let typescript_language = Arc::new(Language::new(
14687        LanguageConfig {
14688            name: "TypeScript".into(),
14689            matcher: LanguageMatcher {
14690                path_suffixes: vec!["ts".to_string()],
14691                ..LanguageMatcher::default()
14692            },
14693            line_comments: vec!["// ".into()],
14694            ..LanguageConfig::default()
14695        },
14696        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14697    ));
14698    language_registry.add(typescript_language.clone());
14699    let mut fake_servers = language_registry.register_fake_lsp(
14700        "TypeScript",
14701        FakeLspAdapter {
14702            capabilities: lsp::ServerCapabilities {
14703                completion_provider: Some(lsp::CompletionOptions {
14704                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14705                    ..lsp::CompletionOptions::default()
14706                }),
14707                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14708                ..lsp::ServerCapabilities::default()
14709            },
14710            // Emulate vtsls label generation
14711            label_for_completion: Some(Box::new(|item, _| {
14712                let text = if let Some(description) = item
14713                    .label_details
14714                    .as_ref()
14715                    .and_then(|label_details| label_details.description.as_ref())
14716                {
14717                    format!("{} {}", item.label, description)
14718                } else if let Some(detail) = &item.detail {
14719                    format!("{} {}", item.label, detail)
14720                } else {
14721                    item.label.clone()
14722                };
14723                let len = text.len();
14724                Some(language::CodeLabel {
14725                    text,
14726                    runs: Vec::new(),
14727                    filter_range: 0..len,
14728                })
14729            })),
14730            ..FakeLspAdapter::default()
14731        },
14732    );
14733    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14734    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14735    let worktree_id = workspace
14736        .update(cx, |workspace, _window, cx| {
14737            workspace.project().update(cx, |project, cx| {
14738                project.worktrees(cx).next().unwrap().read(cx).id()
14739            })
14740        })
14741        .unwrap();
14742    let _buffer = project
14743        .update(cx, |project, cx| {
14744            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
14745        })
14746        .await
14747        .unwrap();
14748    let editor = workspace
14749        .update(cx, |workspace, window, cx| {
14750            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
14751        })
14752        .unwrap()
14753        .await
14754        .unwrap()
14755        .downcast::<Editor>()
14756        .unwrap();
14757    let fake_server = fake_servers.next().await.unwrap();
14758
14759    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
14760    let multiline_label_2 = "a\nb\nc\n";
14761    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
14762    let multiline_description = "d\ne\nf\n";
14763    let multiline_detail_2 = "g\nh\ni\n";
14764
14765    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
14766        move |params, _| async move {
14767            Ok(Some(lsp::CompletionResponse::Array(vec![
14768                lsp::CompletionItem {
14769                    label: multiline_label.to_string(),
14770                    text_edit: gen_text_edit(&params, "new_text_1"),
14771                    ..lsp::CompletionItem::default()
14772                },
14773                lsp::CompletionItem {
14774                    label: "single line label 1".to_string(),
14775                    detail: Some(multiline_detail.to_string()),
14776                    text_edit: gen_text_edit(&params, "new_text_2"),
14777                    ..lsp::CompletionItem::default()
14778                },
14779                lsp::CompletionItem {
14780                    label: "single line label 2".to_string(),
14781                    label_details: Some(lsp::CompletionItemLabelDetails {
14782                        description: Some(multiline_description.to_string()),
14783                        detail: None,
14784                    }),
14785                    text_edit: gen_text_edit(&params, "new_text_2"),
14786                    ..lsp::CompletionItem::default()
14787                },
14788                lsp::CompletionItem {
14789                    label: multiline_label_2.to_string(),
14790                    detail: Some(multiline_detail_2.to_string()),
14791                    text_edit: gen_text_edit(&params, "new_text_3"),
14792                    ..lsp::CompletionItem::default()
14793                },
14794                lsp::CompletionItem {
14795                    label: "Label with many     spaces and \t but without newlines".to_string(),
14796                    detail: Some(
14797                        "Details with many     spaces and \t but without newlines".to_string(),
14798                    ),
14799                    text_edit: gen_text_edit(&params, "new_text_4"),
14800                    ..lsp::CompletionItem::default()
14801                },
14802            ])))
14803        },
14804    );
14805
14806    editor.update_in(cx, |editor, window, cx| {
14807        cx.focus_self(window);
14808        editor.move_to_end(&MoveToEnd, window, cx);
14809        editor.handle_input(".", window, cx);
14810    });
14811    cx.run_until_parked();
14812    completion_handle.next().await.unwrap();
14813
14814    editor.update(cx, |editor, _| {
14815        assert!(editor.context_menu_visible());
14816        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14817        {
14818            let completion_labels = menu
14819                .completions
14820                .borrow()
14821                .iter()
14822                .map(|c| c.label.text.clone())
14823                .collect::<Vec<_>>();
14824            assert_eq!(
14825                completion_labels,
14826                &[
14827                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
14828                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
14829                    "single line label 2 d e f ",
14830                    "a b c g h i ",
14831                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
14832                ],
14833                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
14834            );
14835
14836            for completion in menu
14837                .completions
14838                .borrow()
14839                .iter() {
14840                    assert_eq!(
14841                        completion.label.filter_range,
14842                        0..completion.label.text.len(),
14843                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
14844                    );
14845                }
14846        } else {
14847            panic!("expected completion menu to be open");
14848        }
14849    });
14850}
14851
14852#[gpui::test]
14853async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
14854    init_test(cx, |_| {});
14855    let mut cx = EditorLspTestContext::new_rust(
14856        lsp::ServerCapabilities {
14857            completion_provider: Some(lsp::CompletionOptions {
14858                trigger_characters: Some(vec![".".to_string()]),
14859                ..Default::default()
14860            }),
14861            ..Default::default()
14862        },
14863        cx,
14864    )
14865    .await;
14866    cx.lsp
14867        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14868            Ok(Some(lsp::CompletionResponse::Array(vec![
14869                lsp::CompletionItem {
14870                    label: "first".into(),
14871                    ..Default::default()
14872                },
14873                lsp::CompletionItem {
14874                    label: "last".into(),
14875                    ..Default::default()
14876                },
14877            ])))
14878        });
14879    cx.set_state("variableˇ");
14880    cx.simulate_keystroke(".");
14881    cx.executor().run_until_parked();
14882
14883    cx.update_editor(|editor, _, _| {
14884        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14885        {
14886            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
14887        } else {
14888            panic!("expected completion menu to be open");
14889        }
14890    });
14891
14892    cx.update_editor(|editor, window, cx| {
14893        editor.move_page_down(&MovePageDown::default(), window, cx);
14894        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14895        {
14896            assert!(
14897                menu.selected_item == 1,
14898                "expected PageDown to select the last item from the context menu"
14899            );
14900        } else {
14901            panic!("expected completion menu to stay open after PageDown");
14902        }
14903    });
14904
14905    cx.update_editor(|editor, window, cx| {
14906        editor.move_page_up(&MovePageUp::default(), window, cx);
14907        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14908        {
14909            assert!(
14910                menu.selected_item == 0,
14911                "expected PageUp to select the first item from the context menu"
14912            );
14913        } else {
14914            panic!("expected completion menu to stay open after PageUp");
14915        }
14916    });
14917}
14918
14919#[gpui::test]
14920async fn test_as_is_completions(cx: &mut TestAppContext) {
14921    init_test(cx, |_| {});
14922    let mut cx = EditorLspTestContext::new_rust(
14923        lsp::ServerCapabilities {
14924            completion_provider: Some(lsp::CompletionOptions {
14925                ..Default::default()
14926            }),
14927            ..Default::default()
14928        },
14929        cx,
14930    )
14931    .await;
14932    cx.lsp
14933        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14934            Ok(Some(lsp::CompletionResponse::Array(vec![
14935                lsp::CompletionItem {
14936                    label: "unsafe".into(),
14937                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14938                        range: lsp::Range {
14939                            start: lsp::Position {
14940                                line: 1,
14941                                character: 2,
14942                            },
14943                            end: lsp::Position {
14944                                line: 1,
14945                                character: 3,
14946                            },
14947                        },
14948                        new_text: "unsafe".to_string(),
14949                    })),
14950                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
14951                    ..Default::default()
14952                },
14953            ])))
14954        });
14955    cx.set_state("fn a() {}\n");
14956    cx.executor().run_until_parked();
14957    cx.update_editor(|editor, window, cx| {
14958        editor.show_completions(
14959            &ShowCompletions {
14960                trigger: Some("\n".into()),
14961            },
14962            window,
14963            cx,
14964        );
14965    });
14966    cx.executor().run_until_parked();
14967
14968    cx.update_editor(|editor, window, cx| {
14969        editor.confirm_completion(&Default::default(), window, cx)
14970    });
14971    cx.executor().run_until_parked();
14972    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
14973}
14974
14975#[gpui::test]
14976async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
14977    init_test(cx, |_| {});
14978    let language =
14979        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
14980    let mut cx = EditorLspTestContext::new(
14981        language,
14982        lsp::ServerCapabilities {
14983            completion_provider: Some(lsp::CompletionOptions {
14984                ..lsp::CompletionOptions::default()
14985            }),
14986            ..lsp::ServerCapabilities::default()
14987        },
14988        cx,
14989    )
14990    .await;
14991
14992    cx.set_state(
14993        "#ifndef BAR_H
14994#define BAR_H
14995
14996#include <stdbool.h>
14997
14998int fn_branch(bool do_branch1, bool do_branch2);
14999
15000#endif // BAR_H
15001ˇ",
15002    );
15003    cx.executor().run_until_parked();
15004    cx.update_editor(|editor, window, cx| {
15005        editor.handle_input("#", window, cx);
15006    });
15007    cx.executor().run_until_parked();
15008    cx.update_editor(|editor, window, cx| {
15009        editor.handle_input("i", window, cx);
15010    });
15011    cx.executor().run_until_parked();
15012    cx.update_editor(|editor, window, cx| {
15013        editor.handle_input("n", window, cx);
15014    });
15015    cx.executor().run_until_parked();
15016    cx.assert_editor_state(
15017        "#ifndef BAR_H
15018#define BAR_H
15019
15020#include <stdbool.h>
15021
15022int fn_branch(bool do_branch1, bool do_branch2);
15023
15024#endif // BAR_H
15025#inˇ",
15026    );
15027
15028    cx.lsp
15029        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15030            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15031                is_incomplete: false,
15032                item_defaults: None,
15033                items: vec![lsp::CompletionItem {
15034                    kind: Some(lsp::CompletionItemKind::SNIPPET),
15035                    label_details: Some(lsp::CompletionItemLabelDetails {
15036                        detail: Some("header".to_string()),
15037                        description: None,
15038                    }),
15039                    label: " include".to_string(),
15040                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15041                        range: lsp::Range {
15042                            start: lsp::Position {
15043                                line: 8,
15044                                character: 1,
15045                            },
15046                            end: lsp::Position {
15047                                line: 8,
15048                                character: 1,
15049                            },
15050                        },
15051                        new_text: "include \"$0\"".to_string(),
15052                    })),
15053                    sort_text: Some("40b67681include".to_string()),
15054                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15055                    filter_text: Some("include".to_string()),
15056                    insert_text: Some("include \"$0\"".to_string()),
15057                    ..lsp::CompletionItem::default()
15058                }],
15059            })))
15060        });
15061    cx.update_editor(|editor, window, cx| {
15062        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15063    });
15064    cx.executor().run_until_parked();
15065    cx.update_editor(|editor, window, cx| {
15066        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15067    });
15068    cx.executor().run_until_parked();
15069    cx.assert_editor_state(
15070        "#ifndef BAR_H
15071#define BAR_H
15072
15073#include <stdbool.h>
15074
15075int fn_branch(bool do_branch1, bool do_branch2);
15076
15077#endif // BAR_H
15078#include \"ˇ\"",
15079    );
15080
15081    cx.lsp
15082        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15083            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15084                is_incomplete: true,
15085                item_defaults: None,
15086                items: vec![lsp::CompletionItem {
15087                    kind: Some(lsp::CompletionItemKind::FILE),
15088                    label: "AGL/".to_string(),
15089                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15090                        range: lsp::Range {
15091                            start: lsp::Position {
15092                                line: 8,
15093                                character: 10,
15094                            },
15095                            end: lsp::Position {
15096                                line: 8,
15097                                character: 11,
15098                            },
15099                        },
15100                        new_text: "AGL/".to_string(),
15101                    })),
15102                    sort_text: Some("40b67681AGL/".to_string()),
15103                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15104                    filter_text: Some("AGL/".to_string()),
15105                    insert_text: Some("AGL/".to_string()),
15106                    ..lsp::CompletionItem::default()
15107                }],
15108            })))
15109        });
15110    cx.update_editor(|editor, window, cx| {
15111        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15112    });
15113    cx.executor().run_until_parked();
15114    cx.update_editor(|editor, window, cx| {
15115        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15116    });
15117    cx.executor().run_until_parked();
15118    cx.assert_editor_state(
15119        r##"#ifndef BAR_H
15120#define BAR_H
15121
15122#include <stdbool.h>
15123
15124int fn_branch(bool do_branch1, bool do_branch2);
15125
15126#endif // BAR_H
15127#include "AGL/ˇ"##,
15128    );
15129
15130    cx.update_editor(|editor, window, cx| {
15131        editor.handle_input("\"", window, cx);
15132    });
15133    cx.executor().run_until_parked();
15134    cx.assert_editor_state(
15135        r##"#ifndef BAR_H
15136#define BAR_H
15137
15138#include <stdbool.h>
15139
15140int fn_branch(bool do_branch1, bool do_branch2);
15141
15142#endif // BAR_H
15143#include "AGL/"ˇ"##,
15144    );
15145}
15146
15147#[gpui::test]
15148async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15149    init_test(cx, |_| {});
15150
15151    let mut cx = EditorLspTestContext::new_rust(
15152        lsp::ServerCapabilities {
15153            completion_provider: Some(lsp::CompletionOptions {
15154                trigger_characters: Some(vec![".".to_string()]),
15155                resolve_provider: Some(true),
15156                ..Default::default()
15157            }),
15158            ..Default::default()
15159        },
15160        cx,
15161    )
15162    .await;
15163
15164    cx.set_state("fn main() { let a = 2ˇ; }");
15165    cx.simulate_keystroke(".");
15166    let completion_item = lsp::CompletionItem {
15167        label: "Some".into(),
15168        kind: Some(lsp::CompletionItemKind::SNIPPET),
15169        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15170        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15171            kind: lsp::MarkupKind::Markdown,
15172            value: "```rust\nSome(2)\n```".to_string(),
15173        })),
15174        deprecated: Some(false),
15175        sort_text: Some("Some".to_string()),
15176        filter_text: Some("Some".to_string()),
15177        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15178        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15179            range: lsp::Range {
15180                start: lsp::Position {
15181                    line: 0,
15182                    character: 22,
15183                },
15184                end: lsp::Position {
15185                    line: 0,
15186                    character: 22,
15187                },
15188            },
15189            new_text: "Some(2)".to_string(),
15190        })),
15191        additional_text_edits: Some(vec![lsp::TextEdit {
15192            range: lsp::Range {
15193                start: lsp::Position {
15194                    line: 0,
15195                    character: 20,
15196                },
15197                end: lsp::Position {
15198                    line: 0,
15199                    character: 22,
15200                },
15201            },
15202            new_text: "".to_string(),
15203        }]),
15204        ..Default::default()
15205    };
15206
15207    let closure_completion_item = completion_item.clone();
15208    let counter = Arc::new(AtomicUsize::new(0));
15209    let counter_clone = counter.clone();
15210    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15211        let task_completion_item = closure_completion_item.clone();
15212        counter_clone.fetch_add(1, atomic::Ordering::Release);
15213        async move {
15214            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15215                is_incomplete: true,
15216                item_defaults: None,
15217                items: vec![task_completion_item],
15218            })))
15219        }
15220    });
15221
15222    cx.condition(|editor, _| editor.context_menu_visible())
15223        .await;
15224    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15225    assert!(request.next().await.is_some());
15226    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15227
15228    cx.simulate_keystrokes("S o m");
15229    cx.condition(|editor, _| editor.context_menu_visible())
15230        .await;
15231    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15232    assert!(request.next().await.is_some());
15233    assert!(request.next().await.is_some());
15234    assert!(request.next().await.is_some());
15235    request.close();
15236    assert!(request.next().await.is_none());
15237    assert_eq!(
15238        counter.load(atomic::Ordering::Acquire),
15239        4,
15240        "With the completions menu open, only one LSP request should happen per input"
15241    );
15242}
15243
15244#[gpui::test]
15245async fn test_toggle_comment(cx: &mut TestAppContext) {
15246    init_test(cx, |_| {});
15247    let mut cx = EditorTestContext::new(cx).await;
15248    let language = Arc::new(Language::new(
15249        LanguageConfig {
15250            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15251            ..Default::default()
15252        },
15253        Some(tree_sitter_rust::LANGUAGE.into()),
15254    ));
15255    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15256
15257    // If multiple selections intersect a line, the line is only toggled once.
15258    cx.set_state(indoc! {"
15259        fn a() {
15260            «//b();
15261            ˇ»// «c();
15262            //ˇ»  d();
15263        }
15264    "});
15265
15266    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15267
15268    cx.assert_editor_state(indoc! {"
15269        fn a() {
15270            «b();
15271            c();
15272            ˇ» d();
15273        }
15274    "});
15275
15276    // The comment prefix is inserted at the same column for every line in a
15277    // selection.
15278    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15279
15280    cx.assert_editor_state(indoc! {"
15281        fn a() {
15282            // «b();
15283            // c();
15284            ˇ»//  d();
15285        }
15286    "});
15287
15288    // If a selection ends at the beginning of a line, that line is not toggled.
15289    cx.set_selections_state(indoc! {"
15290        fn a() {
15291            // b();
15292            «// c();
15293        ˇ»    //  d();
15294        }
15295    "});
15296
15297    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15298
15299    cx.assert_editor_state(indoc! {"
15300        fn a() {
15301            // b();
15302            «c();
15303        ˇ»    //  d();
15304        }
15305    "});
15306
15307    // If a selection span a single line and is empty, the line is toggled.
15308    cx.set_state(indoc! {"
15309        fn a() {
15310            a();
15311            b();
15312        ˇ
15313        }
15314    "});
15315
15316    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15317
15318    cx.assert_editor_state(indoc! {"
15319        fn a() {
15320            a();
15321            b();
15322        //•ˇ
15323        }
15324    "});
15325
15326    // If a selection span multiple lines, empty lines are not toggled.
15327    cx.set_state(indoc! {"
15328        fn a() {
15329            «a();
15330
15331            c();ˇ»
15332        }
15333    "});
15334
15335    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15336
15337    cx.assert_editor_state(indoc! {"
15338        fn a() {
15339            // «a();
15340
15341            // c();ˇ»
15342        }
15343    "});
15344
15345    // If a selection includes multiple comment prefixes, all lines are uncommented.
15346    cx.set_state(indoc! {"
15347        fn a() {
15348            «// a();
15349            /// b();
15350            //! c();ˇ»
15351        }
15352    "});
15353
15354    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15355
15356    cx.assert_editor_state(indoc! {"
15357        fn a() {
15358            «a();
15359            b();
15360            c();ˇ»
15361        }
15362    "});
15363}
15364
15365#[gpui::test]
15366async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15367    init_test(cx, |_| {});
15368    let mut cx = EditorTestContext::new(cx).await;
15369    let language = Arc::new(Language::new(
15370        LanguageConfig {
15371            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15372            ..Default::default()
15373        },
15374        Some(tree_sitter_rust::LANGUAGE.into()),
15375    ));
15376    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15377
15378    let toggle_comments = &ToggleComments {
15379        advance_downwards: false,
15380        ignore_indent: true,
15381    };
15382
15383    // If multiple selections intersect a line, the line is only toggled once.
15384    cx.set_state(indoc! {"
15385        fn a() {
15386        //    «b();
15387        //    c();
15388        //    ˇ» d();
15389        }
15390    "});
15391
15392    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15393
15394    cx.assert_editor_state(indoc! {"
15395        fn a() {
15396            «b();
15397            c();
15398            ˇ» d();
15399        }
15400    "});
15401
15402    // The comment prefix is inserted at the beginning of each line
15403    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15404
15405    cx.assert_editor_state(indoc! {"
15406        fn a() {
15407        //    «b();
15408        //    c();
15409        //    ˇ» d();
15410        }
15411    "});
15412
15413    // If a selection ends at the beginning of a line, that line is not toggled.
15414    cx.set_selections_state(indoc! {"
15415        fn a() {
15416        //    b();
15417        //    «c();
15418        ˇ»//     d();
15419        }
15420    "});
15421
15422    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15423
15424    cx.assert_editor_state(indoc! {"
15425        fn a() {
15426        //    b();
15427            «c();
15428        ˇ»//     d();
15429        }
15430    "});
15431
15432    // If a selection span a single line and is empty, the line is toggled.
15433    cx.set_state(indoc! {"
15434        fn a() {
15435            a();
15436            b();
15437        ˇ
15438        }
15439    "});
15440
15441    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15442
15443    cx.assert_editor_state(indoc! {"
15444        fn a() {
15445            a();
15446            b();
15447        //ˇ
15448        }
15449    "});
15450
15451    // If a selection span multiple lines, empty lines are not toggled.
15452    cx.set_state(indoc! {"
15453        fn a() {
15454            «a();
15455
15456            c();ˇ»
15457        }
15458    "});
15459
15460    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15461
15462    cx.assert_editor_state(indoc! {"
15463        fn a() {
15464        //    «a();
15465
15466        //    c();ˇ»
15467        }
15468    "});
15469
15470    // If a selection includes multiple comment prefixes, all lines are uncommented.
15471    cx.set_state(indoc! {"
15472        fn a() {
15473        //    «a();
15474        ///    b();
15475        //!    c();ˇ»
15476        }
15477    "});
15478
15479    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15480
15481    cx.assert_editor_state(indoc! {"
15482        fn a() {
15483            «a();
15484            b();
15485            c();ˇ»
15486        }
15487    "});
15488}
15489
15490#[gpui::test]
15491async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15492    init_test(cx, |_| {});
15493
15494    let language = Arc::new(Language::new(
15495        LanguageConfig {
15496            line_comments: vec!["// ".into()],
15497            ..Default::default()
15498        },
15499        Some(tree_sitter_rust::LANGUAGE.into()),
15500    ));
15501
15502    let mut cx = EditorTestContext::new(cx).await;
15503
15504    cx.language_registry().add(language.clone());
15505    cx.update_buffer(|buffer, cx| {
15506        buffer.set_language(Some(language), cx);
15507    });
15508
15509    let toggle_comments = &ToggleComments {
15510        advance_downwards: true,
15511        ignore_indent: false,
15512    };
15513
15514    // Single cursor on one line -> advance
15515    // Cursor moves horizontally 3 characters as well on non-blank line
15516    cx.set_state(indoc!(
15517        "fn a() {
15518             ˇdog();
15519             cat();
15520        }"
15521    ));
15522    cx.update_editor(|editor, window, cx| {
15523        editor.toggle_comments(toggle_comments, window, cx);
15524    });
15525    cx.assert_editor_state(indoc!(
15526        "fn a() {
15527             // dog();
15528             catˇ();
15529        }"
15530    ));
15531
15532    // Single selection on one line -> don't advance
15533    cx.set_state(indoc!(
15534        "fn a() {
15535             «dog()ˇ»;
15536             cat();
15537        }"
15538    ));
15539    cx.update_editor(|editor, window, cx| {
15540        editor.toggle_comments(toggle_comments, window, cx);
15541    });
15542    cx.assert_editor_state(indoc!(
15543        "fn a() {
15544             // «dog()ˇ»;
15545             cat();
15546        }"
15547    ));
15548
15549    // Multiple cursors on one line -> advance
15550    cx.set_state(indoc!(
15551        "fn a() {
15552             ˇdˇog();
15553             cat();
15554        }"
15555    ));
15556    cx.update_editor(|editor, window, cx| {
15557        editor.toggle_comments(toggle_comments, window, cx);
15558    });
15559    cx.assert_editor_state(indoc!(
15560        "fn a() {
15561             // dog();
15562             catˇ(ˇ);
15563        }"
15564    ));
15565
15566    // Multiple cursors on one line, with selection -> don't advance
15567    cx.set_state(indoc!(
15568        "fn a() {
15569             ˇdˇog«()ˇ»;
15570             cat();
15571        }"
15572    ));
15573    cx.update_editor(|editor, window, cx| {
15574        editor.toggle_comments(toggle_comments, window, cx);
15575    });
15576    cx.assert_editor_state(indoc!(
15577        "fn a() {
15578             // ˇdˇog«()ˇ»;
15579             cat();
15580        }"
15581    ));
15582
15583    // Single cursor on one line -> advance
15584    // Cursor moves to column 0 on blank line
15585    cx.set_state(indoc!(
15586        "fn a() {
15587             ˇdog();
15588
15589             cat();
15590        }"
15591    ));
15592    cx.update_editor(|editor, window, cx| {
15593        editor.toggle_comments(toggle_comments, window, cx);
15594    });
15595    cx.assert_editor_state(indoc!(
15596        "fn a() {
15597             // dog();
15598        ˇ
15599             cat();
15600        }"
15601    ));
15602
15603    // Single cursor on one line -> advance
15604    // Cursor starts and ends at column 0
15605    cx.set_state(indoc!(
15606        "fn a() {
15607         ˇ    dog();
15608             cat();
15609        }"
15610    ));
15611    cx.update_editor(|editor, window, cx| {
15612        editor.toggle_comments(toggle_comments, window, cx);
15613    });
15614    cx.assert_editor_state(indoc!(
15615        "fn a() {
15616             // dog();
15617         ˇ    cat();
15618        }"
15619    ));
15620}
15621
15622#[gpui::test]
15623async fn test_toggle_block_comment(cx: &mut TestAppContext) {
15624    init_test(cx, |_| {});
15625
15626    let mut cx = EditorTestContext::new(cx).await;
15627
15628    let html_language = Arc::new(
15629        Language::new(
15630            LanguageConfig {
15631                name: "HTML".into(),
15632                block_comment: Some(BlockCommentConfig {
15633                    start: "<!-- ".into(),
15634                    prefix: "".into(),
15635                    end: " -->".into(),
15636                    tab_size: 0,
15637                }),
15638                ..Default::default()
15639            },
15640            Some(tree_sitter_html::LANGUAGE.into()),
15641        )
15642        .with_injection_query(
15643            r#"
15644            (script_element
15645                (raw_text) @injection.content
15646                (#set! injection.language "javascript"))
15647            "#,
15648        )
15649        .unwrap(),
15650    );
15651
15652    let javascript_language = Arc::new(Language::new(
15653        LanguageConfig {
15654            name: "JavaScript".into(),
15655            line_comments: vec!["// ".into()],
15656            ..Default::default()
15657        },
15658        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15659    ));
15660
15661    cx.language_registry().add(html_language.clone());
15662    cx.language_registry().add(javascript_language);
15663    cx.update_buffer(|buffer, cx| {
15664        buffer.set_language(Some(html_language), cx);
15665    });
15666
15667    // Toggle comments for empty selections
15668    cx.set_state(
15669        &r#"
15670            <p>A</p>ˇ
15671            <p>B</p>ˇ
15672            <p>C</p>ˇ
15673        "#
15674        .unindent(),
15675    );
15676    cx.update_editor(|editor, window, cx| {
15677        editor.toggle_comments(&ToggleComments::default(), window, cx)
15678    });
15679    cx.assert_editor_state(
15680        &r#"
15681            <!-- <p>A</p>ˇ -->
15682            <!-- <p>B</p>ˇ -->
15683            <!-- <p>C</p>ˇ -->
15684        "#
15685        .unindent(),
15686    );
15687    cx.update_editor(|editor, window, cx| {
15688        editor.toggle_comments(&ToggleComments::default(), window, cx)
15689    });
15690    cx.assert_editor_state(
15691        &r#"
15692            <p>A</p>ˇ
15693            <p>B</p>ˇ
15694            <p>C</p>ˇ
15695        "#
15696        .unindent(),
15697    );
15698
15699    // Toggle comments for mixture of empty and non-empty selections, where
15700    // multiple selections occupy a given line.
15701    cx.set_state(
15702        &r#"
15703            <p>A«</p>
15704            <p>ˇ»B</p>ˇ
15705            <p>C«</p>
15706            <p>ˇ»D</p>ˇ
15707        "#
15708        .unindent(),
15709    );
15710
15711    cx.update_editor(|editor, window, cx| {
15712        editor.toggle_comments(&ToggleComments::default(), window, cx)
15713    });
15714    cx.assert_editor_state(
15715        &r#"
15716            <!-- <p>A«</p>
15717            <p>ˇ»B</p>ˇ -->
15718            <!-- <p>C«</p>
15719            <p>ˇ»D</p>ˇ -->
15720        "#
15721        .unindent(),
15722    );
15723    cx.update_editor(|editor, window, cx| {
15724        editor.toggle_comments(&ToggleComments::default(), window, cx)
15725    });
15726    cx.assert_editor_state(
15727        &r#"
15728            <p>A«</p>
15729            <p>ˇ»B</p>ˇ
15730            <p>C«</p>
15731            <p>ˇ»D</p>ˇ
15732        "#
15733        .unindent(),
15734    );
15735
15736    // Toggle comments when different languages are active for different
15737    // selections.
15738    cx.set_state(
15739        &r#"
15740            ˇ<script>
15741                ˇvar x = new Y();
15742            ˇ</script>
15743        "#
15744        .unindent(),
15745    );
15746    cx.executor().run_until_parked();
15747    cx.update_editor(|editor, window, cx| {
15748        editor.toggle_comments(&ToggleComments::default(), window, cx)
15749    });
15750    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
15751    // Uncommenting and commenting from this position brings in even more wrong artifacts.
15752    cx.assert_editor_state(
15753        &r#"
15754            <!-- ˇ<script> -->
15755                // ˇvar x = new Y();
15756            <!-- ˇ</script> -->
15757        "#
15758        .unindent(),
15759    );
15760}
15761
15762#[gpui::test]
15763fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
15764    init_test(cx, |_| {});
15765
15766    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15767    let multibuffer = cx.new(|cx| {
15768        let mut multibuffer = MultiBuffer::new(ReadWrite);
15769        multibuffer.push_excerpts(
15770            buffer.clone(),
15771            [
15772                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
15773                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
15774            ],
15775            cx,
15776        );
15777        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
15778        multibuffer
15779    });
15780
15781    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15782    editor.update_in(cx, |editor, window, cx| {
15783        assert_eq!(editor.text(cx), "aaaa\nbbbb");
15784        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15785            s.select_ranges([
15786                Point::new(0, 0)..Point::new(0, 0),
15787                Point::new(1, 0)..Point::new(1, 0),
15788            ])
15789        });
15790
15791        editor.handle_input("X", window, cx);
15792        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
15793        assert_eq!(
15794            editor.selections.ranges(cx),
15795            [
15796                Point::new(0, 1)..Point::new(0, 1),
15797                Point::new(1, 1)..Point::new(1, 1),
15798            ]
15799        );
15800
15801        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
15802        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15803            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
15804        });
15805        editor.backspace(&Default::default(), window, cx);
15806        assert_eq!(editor.text(cx), "Xa\nbbb");
15807        assert_eq!(
15808            editor.selections.ranges(cx),
15809            [Point::new(1, 0)..Point::new(1, 0)]
15810        );
15811
15812        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15813            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
15814        });
15815        editor.backspace(&Default::default(), window, cx);
15816        assert_eq!(editor.text(cx), "X\nbb");
15817        assert_eq!(
15818            editor.selections.ranges(cx),
15819            [Point::new(0, 1)..Point::new(0, 1)]
15820        );
15821    });
15822}
15823
15824#[gpui::test]
15825fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
15826    init_test(cx, |_| {});
15827
15828    let markers = vec![('[', ']').into(), ('(', ')').into()];
15829    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
15830        indoc! {"
15831            [aaaa
15832            (bbbb]
15833            cccc)",
15834        },
15835        markers.clone(),
15836    );
15837    let excerpt_ranges = markers.into_iter().map(|marker| {
15838        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
15839        ExcerptRange::new(context)
15840    });
15841    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
15842    let multibuffer = cx.new(|cx| {
15843        let mut multibuffer = MultiBuffer::new(ReadWrite);
15844        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
15845        multibuffer
15846    });
15847
15848    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15849    editor.update_in(cx, |editor, window, cx| {
15850        let (expected_text, selection_ranges) = marked_text_ranges(
15851            indoc! {"
15852                aaaa
15853                bˇbbb
15854                bˇbbˇb
15855                cccc"
15856            },
15857            true,
15858        );
15859        assert_eq!(editor.text(cx), expected_text);
15860        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15861            s.select_ranges(selection_ranges)
15862        });
15863
15864        editor.handle_input("X", window, cx);
15865
15866        let (expected_text, expected_selections) = marked_text_ranges(
15867            indoc! {"
15868                aaaa
15869                bXˇbbXb
15870                bXˇbbXˇb
15871                cccc"
15872            },
15873            false,
15874        );
15875        assert_eq!(editor.text(cx), expected_text);
15876        assert_eq!(editor.selections.ranges(cx), expected_selections);
15877
15878        editor.newline(&Newline, window, cx);
15879        let (expected_text, expected_selections) = marked_text_ranges(
15880            indoc! {"
15881                aaaa
15882                bX
15883                ˇbbX
15884                b
15885                bX
15886                ˇbbX
15887                ˇb
15888                cccc"
15889            },
15890            false,
15891        );
15892        assert_eq!(editor.text(cx), expected_text);
15893        assert_eq!(editor.selections.ranges(cx), expected_selections);
15894    });
15895}
15896
15897#[gpui::test]
15898fn test_refresh_selections(cx: &mut TestAppContext) {
15899    init_test(cx, |_| {});
15900
15901    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15902    let mut excerpt1_id = None;
15903    let multibuffer = cx.new(|cx| {
15904        let mut multibuffer = MultiBuffer::new(ReadWrite);
15905        excerpt1_id = multibuffer
15906            .push_excerpts(
15907                buffer.clone(),
15908                [
15909                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15910                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15911                ],
15912                cx,
15913            )
15914            .into_iter()
15915            .next();
15916        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15917        multibuffer
15918    });
15919
15920    let editor = cx.add_window(|window, cx| {
15921        let mut editor = build_editor(multibuffer.clone(), window, cx);
15922        let snapshot = editor.snapshot(window, cx);
15923        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15924            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
15925        });
15926        editor.begin_selection(
15927            Point::new(2, 1).to_display_point(&snapshot),
15928            true,
15929            1,
15930            window,
15931            cx,
15932        );
15933        assert_eq!(
15934            editor.selections.ranges(cx),
15935            [
15936                Point::new(1, 3)..Point::new(1, 3),
15937                Point::new(2, 1)..Point::new(2, 1),
15938            ]
15939        );
15940        editor
15941    });
15942
15943    // Refreshing selections is a no-op when excerpts haven't changed.
15944    _ = editor.update(cx, |editor, window, cx| {
15945        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15946        assert_eq!(
15947            editor.selections.ranges(cx),
15948            [
15949                Point::new(1, 3)..Point::new(1, 3),
15950                Point::new(2, 1)..Point::new(2, 1),
15951            ]
15952        );
15953    });
15954
15955    multibuffer.update(cx, |multibuffer, cx| {
15956        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
15957    });
15958    _ = editor.update(cx, |editor, window, cx| {
15959        // Removing an excerpt causes the first selection to become degenerate.
15960        assert_eq!(
15961            editor.selections.ranges(cx),
15962            [
15963                Point::new(0, 0)..Point::new(0, 0),
15964                Point::new(0, 1)..Point::new(0, 1)
15965            ]
15966        );
15967
15968        // Refreshing selections will relocate the first selection to the original buffer
15969        // location.
15970        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15971        assert_eq!(
15972            editor.selections.ranges(cx),
15973            [
15974                Point::new(0, 1)..Point::new(0, 1),
15975                Point::new(0, 3)..Point::new(0, 3)
15976            ]
15977        );
15978        assert!(editor.selections.pending_anchor().is_some());
15979    });
15980}
15981
15982#[gpui::test]
15983fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
15984    init_test(cx, |_| {});
15985
15986    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15987    let mut excerpt1_id = None;
15988    let multibuffer = cx.new(|cx| {
15989        let mut multibuffer = MultiBuffer::new(ReadWrite);
15990        excerpt1_id = multibuffer
15991            .push_excerpts(
15992                buffer.clone(),
15993                [
15994                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15995                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15996                ],
15997                cx,
15998            )
15999            .into_iter()
16000            .next();
16001        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16002        multibuffer
16003    });
16004
16005    let editor = cx.add_window(|window, cx| {
16006        let mut editor = build_editor(multibuffer.clone(), window, cx);
16007        let snapshot = editor.snapshot(window, cx);
16008        editor.begin_selection(
16009            Point::new(1, 3).to_display_point(&snapshot),
16010            false,
16011            1,
16012            window,
16013            cx,
16014        );
16015        assert_eq!(
16016            editor.selections.ranges(cx),
16017            [Point::new(1, 3)..Point::new(1, 3)]
16018        );
16019        editor
16020    });
16021
16022    multibuffer.update(cx, |multibuffer, cx| {
16023        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16024    });
16025    _ = editor.update(cx, |editor, window, cx| {
16026        assert_eq!(
16027            editor.selections.ranges(cx),
16028            [Point::new(0, 0)..Point::new(0, 0)]
16029        );
16030
16031        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
16032        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16033        assert_eq!(
16034            editor.selections.ranges(cx),
16035            [Point::new(0, 3)..Point::new(0, 3)]
16036        );
16037        assert!(editor.selections.pending_anchor().is_some());
16038    });
16039}
16040
16041#[gpui::test]
16042async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
16043    init_test(cx, |_| {});
16044
16045    let language = Arc::new(
16046        Language::new(
16047            LanguageConfig {
16048                brackets: BracketPairConfig {
16049                    pairs: vec![
16050                        BracketPair {
16051                            start: "{".to_string(),
16052                            end: "}".to_string(),
16053                            close: true,
16054                            surround: true,
16055                            newline: true,
16056                        },
16057                        BracketPair {
16058                            start: "/* ".to_string(),
16059                            end: " */".to_string(),
16060                            close: true,
16061                            surround: true,
16062                            newline: true,
16063                        },
16064                    ],
16065                    ..Default::default()
16066                },
16067                ..Default::default()
16068            },
16069            Some(tree_sitter_rust::LANGUAGE.into()),
16070        )
16071        .with_indents_query("")
16072        .unwrap(),
16073    );
16074
16075    let text = concat!(
16076        "{   }\n",     //
16077        "  x\n",       //
16078        "  /*   */\n", //
16079        "x\n",         //
16080        "{{} }\n",     //
16081    );
16082
16083    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16084    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16085    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16086    editor
16087        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16088        .await;
16089
16090    editor.update_in(cx, |editor, window, cx| {
16091        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16092            s.select_display_ranges([
16093                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16094                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16095                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16096            ])
16097        });
16098        editor.newline(&Newline, window, cx);
16099
16100        assert_eq!(
16101            editor.buffer().read(cx).read(cx).text(),
16102            concat!(
16103                "{ \n",    // Suppress rustfmt
16104                "\n",      //
16105                "}\n",     //
16106                "  x\n",   //
16107                "  /* \n", //
16108                "  \n",    //
16109                "  */\n",  //
16110                "x\n",     //
16111                "{{} \n",  //
16112                "}\n",     //
16113            )
16114        );
16115    });
16116}
16117
16118#[gpui::test]
16119fn test_highlighted_ranges(cx: &mut TestAppContext) {
16120    init_test(cx, |_| {});
16121
16122    let editor = cx.add_window(|window, cx| {
16123        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16124        build_editor(buffer, window, cx)
16125    });
16126
16127    _ = editor.update(cx, |editor, window, cx| {
16128        struct Type1;
16129        struct Type2;
16130
16131        let buffer = editor.buffer.read(cx).snapshot(cx);
16132
16133        let anchor_range =
16134            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16135
16136        editor.highlight_background::<Type1>(
16137            &[
16138                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16139                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16140                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16141                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16142            ],
16143            |_| Hsla::red(),
16144            cx,
16145        );
16146        editor.highlight_background::<Type2>(
16147            &[
16148                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16149                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16150                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16151                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16152            ],
16153            |_| Hsla::green(),
16154            cx,
16155        );
16156
16157        let snapshot = editor.snapshot(window, cx);
16158        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16159            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16160            &snapshot,
16161            cx.theme(),
16162        );
16163        assert_eq!(
16164            highlighted_ranges,
16165            &[
16166                (
16167                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16168                    Hsla::green(),
16169                ),
16170                (
16171                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16172                    Hsla::red(),
16173                ),
16174                (
16175                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16176                    Hsla::green(),
16177                ),
16178                (
16179                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16180                    Hsla::red(),
16181                ),
16182            ]
16183        );
16184        assert_eq!(
16185            editor.sorted_background_highlights_in_range(
16186                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16187                &snapshot,
16188                cx.theme(),
16189            ),
16190            &[(
16191                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16192                Hsla::red(),
16193            )]
16194        );
16195    });
16196}
16197
16198#[gpui::test]
16199async fn test_following(cx: &mut TestAppContext) {
16200    init_test(cx, |_| {});
16201
16202    let fs = FakeFs::new(cx.executor());
16203    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16204
16205    let buffer = project.update(cx, |project, cx| {
16206        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16207        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16208    });
16209    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16210    let follower = cx.update(|cx| {
16211        cx.open_window(
16212            WindowOptions {
16213                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16214                    gpui::Point::new(px(0.), px(0.)),
16215                    gpui::Point::new(px(10.), px(80.)),
16216                ))),
16217                ..Default::default()
16218            },
16219            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16220        )
16221        .unwrap()
16222    });
16223
16224    let is_still_following = Rc::new(RefCell::new(true));
16225    let follower_edit_event_count = Rc::new(RefCell::new(0));
16226    let pending_update = Rc::new(RefCell::new(None));
16227    let leader_entity = leader.root(cx).unwrap();
16228    let follower_entity = follower.root(cx).unwrap();
16229    _ = follower.update(cx, {
16230        let update = pending_update.clone();
16231        let is_still_following = is_still_following.clone();
16232        let follower_edit_event_count = follower_edit_event_count.clone();
16233        |_, window, cx| {
16234            cx.subscribe_in(
16235                &leader_entity,
16236                window,
16237                move |_, leader, event, window, cx| {
16238                    leader.read(cx).add_event_to_update_proto(
16239                        event,
16240                        &mut update.borrow_mut(),
16241                        window,
16242                        cx,
16243                    );
16244                },
16245            )
16246            .detach();
16247
16248            cx.subscribe_in(
16249                &follower_entity,
16250                window,
16251                move |_, _, event: &EditorEvent, _window, _cx| {
16252                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16253                        *is_still_following.borrow_mut() = false;
16254                    }
16255
16256                    if let EditorEvent::BufferEdited = event {
16257                        *follower_edit_event_count.borrow_mut() += 1;
16258                    }
16259                },
16260            )
16261            .detach();
16262        }
16263    });
16264
16265    // Update the selections only
16266    _ = leader.update(cx, |leader, window, cx| {
16267        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16268            s.select_ranges([1..1])
16269        });
16270    });
16271    follower
16272        .update(cx, |follower, window, cx| {
16273            follower.apply_update_proto(
16274                &project,
16275                pending_update.borrow_mut().take().unwrap(),
16276                window,
16277                cx,
16278            )
16279        })
16280        .unwrap()
16281        .await
16282        .unwrap();
16283    _ = follower.update(cx, |follower, _, cx| {
16284        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
16285    });
16286    assert!(*is_still_following.borrow());
16287    assert_eq!(*follower_edit_event_count.borrow(), 0);
16288
16289    // Update the scroll position only
16290    _ = leader.update(cx, |leader, window, cx| {
16291        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16292    });
16293    follower
16294        .update(cx, |follower, window, cx| {
16295            follower.apply_update_proto(
16296                &project,
16297                pending_update.borrow_mut().take().unwrap(),
16298                window,
16299                cx,
16300            )
16301        })
16302        .unwrap()
16303        .await
16304        .unwrap();
16305    assert_eq!(
16306        follower
16307            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16308            .unwrap(),
16309        gpui::Point::new(1.5, 3.5)
16310    );
16311    assert!(*is_still_following.borrow());
16312    assert_eq!(*follower_edit_event_count.borrow(), 0);
16313
16314    // Update the selections and scroll position. The follower's scroll position is updated
16315    // via autoscroll, not via the leader's exact scroll position.
16316    _ = leader.update(cx, |leader, window, cx| {
16317        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16318            s.select_ranges([0..0])
16319        });
16320        leader.request_autoscroll(Autoscroll::newest(), cx);
16321        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16322    });
16323    follower
16324        .update(cx, |follower, window, cx| {
16325            follower.apply_update_proto(
16326                &project,
16327                pending_update.borrow_mut().take().unwrap(),
16328                window,
16329                cx,
16330            )
16331        })
16332        .unwrap()
16333        .await
16334        .unwrap();
16335    _ = follower.update(cx, |follower, _, cx| {
16336        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16337        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
16338    });
16339    assert!(*is_still_following.borrow());
16340
16341    // Creating a pending selection that precedes another selection
16342    _ = leader.update(cx, |leader, window, cx| {
16343        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16344            s.select_ranges([1..1])
16345        });
16346        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16347    });
16348    follower
16349        .update(cx, |follower, window, cx| {
16350            follower.apply_update_proto(
16351                &project,
16352                pending_update.borrow_mut().take().unwrap(),
16353                window,
16354                cx,
16355            )
16356        })
16357        .unwrap()
16358        .await
16359        .unwrap();
16360    _ = follower.update(cx, |follower, _, cx| {
16361        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
16362    });
16363    assert!(*is_still_following.borrow());
16364
16365    // Extend the pending selection so that it surrounds another selection
16366    _ = leader.update(cx, |leader, window, cx| {
16367        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16368    });
16369    follower
16370        .update(cx, |follower, window, cx| {
16371            follower.apply_update_proto(
16372                &project,
16373                pending_update.borrow_mut().take().unwrap(),
16374                window,
16375                cx,
16376            )
16377        })
16378        .unwrap()
16379        .await
16380        .unwrap();
16381    _ = follower.update(cx, |follower, _, cx| {
16382        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
16383    });
16384
16385    // Scrolling locally breaks the follow
16386    _ = follower.update(cx, |follower, window, cx| {
16387        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16388        follower.set_scroll_anchor(
16389            ScrollAnchor {
16390                anchor: top_anchor,
16391                offset: gpui::Point::new(0.0, 0.5),
16392            },
16393            window,
16394            cx,
16395        );
16396    });
16397    assert!(!(*is_still_following.borrow()));
16398}
16399
16400#[gpui::test]
16401async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16402    init_test(cx, |_| {});
16403
16404    let fs = FakeFs::new(cx.executor());
16405    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16406    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16407    let pane = workspace
16408        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16409        .unwrap();
16410
16411    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16412
16413    let leader = pane.update_in(cx, |_, window, cx| {
16414        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16415        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16416    });
16417
16418    // Start following the editor when it has no excerpts.
16419    let mut state_message =
16420        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16421    let workspace_entity = workspace.root(cx).unwrap();
16422    let follower_1 = cx
16423        .update_window(*workspace.deref(), |_, window, cx| {
16424            Editor::from_state_proto(
16425                workspace_entity,
16426                ViewId {
16427                    creator: CollaboratorId::PeerId(PeerId::default()),
16428                    id: 0,
16429                },
16430                &mut state_message,
16431                window,
16432                cx,
16433            )
16434        })
16435        .unwrap()
16436        .unwrap()
16437        .await
16438        .unwrap();
16439
16440    let update_message = Rc::new(RefCell::new(None));
16441    follower_1.update_in(cx, {
16442        let update = update_message.clone();
16443        |_, window, cx| {
16444            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16445                leader.read(cx).add_event_to_update_proto(
16446                    event,
16447                    &mut update.borrow_mut(),
16448                    window,
16449                    cx,
16450                );
16451            })
16452            .detach();
16453        }
16454    });
16455
16456    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16457        (
16458            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16459            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16460        )
16461    });
16462
16463    // Insert some excerpts.
16464    leader.update(cx, |leader, cx| {
16465        leader.buffer.update(cx, |multibuffer, cx| {
16466            multibuffer.set_excerpts_for_path(
16467                PathKey::namespaced(1, "b.txt".into()),
16468                buffer_1.clone(),
16469                vec![
16470                    Point::row_range(0..3),
16471                    Point::row_range(1..6),
16472                    Point::row_range(12..15),
16473                ],
16474                0,
16475                cx,
16476            );
16477            multibuffer.set_excerpts_for_path(
16478                PathKey::namespaced(1, "a.txt".into()),
16479                buffer_2.clone(),
16480                vec![Point::row_range(0..6), Point::row_range(8..12)],
16481                0,
16482                cx,
16483            );
16484        });
16485    });
16486
16487    // Apply the update of adding the excerpts.
16488    follower_1
16489        .update_in(cx, |follower, window, cx| {
16490            follower.apply_update_proto(
16491                &project,
16492                update_message.borrow().clone().unwrap(),
16493                window,
16494                cx,
16495            )
16496        })
16497        .await
16498        .unwrap();
16499    assert_eq!(
16500        follower_1.update(cx, |editor, cx| editor.text(cx)),
16501        leader.update(cx, |editor, cx| editor.text(cx))
16502    );
16503    update_message.borrow_mut().take();
16504
16505    // Start following separately after it already has excerpts.
16506    let mut state_message =
16507        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16508    let workspace_entity = workspace.root(cx).unwrap();
16509    let follower_2 = cx
16510        .update_window(*workspace.deref(), |_, window, cx| {
16511            Editor::from_state_proto(
16512                workspace_entity,
16513                ViewId {
16514                    creator: CollaboratorId::PeerId(PeerId::default()),
16515                    id: 0,
16516                },
16517                &mut state_message,
16518                window,
16519                cx,
16520            )
16521        })
16522        .unwrap()
16523        .unwrap()
16524        .await
16525        .unwrap();
16526    assert_eq!(
16527        follower_2.update(cx, |editor, cx| editor.text(cx)),
16528        leader.update(cx, |editor, cx| editor.text(cx))
16529    );
16530
16531    // Remove some excerpts.
16532    leader.update(cx, |leader, cx| {
16533        leader.buffer.update(cx, |multibuffer, cx| {
16534            let excerpt_ids = multibuffer.excerpt_ids();
16535            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16536            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16537        });
16538    });
16539
16540    // Apply the update of removing the excerpts.
16541    follower_1
16542        .update_in(cx, |follower, window, cx| {
16543            follower.apply_update_proto(
16544                &project,
16545                update_message.borrow().clone().unwrap(),
16546                window,
16547                cx,
16548            )
16549        })
16550        .await
16551        .unwrap();
16552    follower_2
16553        .update_in(cx, |follower, window, cx| {
16554            follower.apply_update_proto(
16555                &project,
16556                update_message.borrow().clone().unwrap(),
16557                window,
16558                cx,
16559            )
16560        })
16561        .await
16562        .unwrap();
16563    update_message.borrow_mut().take();
16564    assert_eq!(
16565        follower_1.update(cx, |editor, cx| editor.text(cx)),
16566        leader.update(cx, |editor, cx| editor.text(cx))
16567    );
16568}
16569
16570#[gpui::test]
16571async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16572    init_test(cx, |_| {});
16573
16574    let mut cx = EditorTestContext::new(cx).await;
16575    let lsp_store =
16576        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
16577
16578    cx.set_state(indoc! {"
16579        ˇfn func(abc def: i32) -> u32 {
16580        }
16581    "});
16582
16583    cx.update(|_, cx| {
16584        lsp_store.update(cx, |lsp_store, cx| {
16585            lsp_store
16586                .update_diagnostics(
16587                    LanguageServerId(0),
16588                    lsp::PublishDiagnosticsParams {
16589                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
16590                        version: None,
16591                        diagnostics: vec![
16592                            lsp::Diagnostic {
16593                                range: lsp::Range::new(
16594                                    lsp::Position::new(0, 11),
16595                                    lsp::Position::new(0, 12),
16596                                ),
16597                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16598                                ..Default::default()
16599                            },
16600                            lsp::Diagnostic {
16601                                range: lsp::Range::new(
16602                                    lsp::Position::new(0, 12),
16603                                    lsp::Position::new(0, 15),
16604                                ),
16605                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16606                                ..Default::default()
16607                            },
16608                            lsp::Diagnostic {
16609                                range: lsp::Range::new(
16610                                    lsp::Position::new(0, 25),
16611                                    lsp::Position::new(0, 28),
16612                                ),
16613                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16614                                ..Default::default()
16615                            },
16616                        ],
16617                    },
16618                    None,
16619                    DiagnosticSourceKind::Pushed,
16620                    &[],
16621                    cx,
16622                )
16623                .unwrap()
16624        });
16625    });
16626
16627    executor.run_until_parked();
16628
16629    cx.update_editor(|editor, window, cx| {
16630        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16631    });
16632
16633    cx.assert_editor_state(indoc! {"
16634        fn func(abc def: i32) -> ˇu32 {
16635        }
16636    "});
16637
16638    cx.update_editor(|editor, window, cx| {
16639        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16640    });
16641
16642    cx.assert_editor_state(indoc! {"
16643        fn func(abc ˇdef: i32) -> u32 {
16644        }
16645    "});
16646
16647    cx.update_editor(|editor, window, cx| {
16648        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16649    });
16650
16651    cx.assert_editor_state(indoc! {"
16652        fn func(abcˇ def: i32) -> u32 {
16653        }
16654    "});
16655
16656    cx.update_editor(|editor, window, cx| {
16657        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16658    });
16659
16660    cx.assert_editor_state(indoc! {"
16661        fn func(abc def: i32) -> ˇu32 {
16662        }
16663    "});
16664}
16665
16666#[gpui::test]
16667async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16668    init_test(cx, |_| {});
16669
16670    let mut cx = EditorTestContext::new(cx).await;
16671
16672    let diff_base = r#"
16673        use some::mod;
16674
16675        const A: u32 = 42;
16676
16677        fn main() {
16678            println!("hello");
16679
16680            println!("world");
16681        }
16682        "#
16683    .unindent();
16684
16685    // Edits are modified, removed, modified, added
16686    cx.set_state(
16687        &r#"
16688        use some::modified;
16689
16690        ˇ
16691        fn main() {
16692            println!("hello there");
16693
16694            println!("around the");
16695            println!("world");
16696        }
16697        "#
16698        .unindent(),
16699    );
16700
16701    cx.set_head_text(&diff_base);
16702    executor.run_until_parked();
16703
16704    cx.update_editor(|editor, window, cx| {
16705        //Wrap around the bottom of the buffer
16706        for _ in 0..3 {
16707            editor.go_to_next_hunk(&GoToHunk, window, cx);
16708        }
16709    });
16710
16711    cx.assert_editor_state(
16712        &r#"
16713        ˇuse some::modified;
16714
16715
16716        fn main() {
16717            println!("hello there");
16718
16719            println!("around the");
16720            println!("world");
16721        }
16722        "#
16723        .unindent(),
16724    );
16725
16726    cx.update_editor(|editor, window, cx| {
16727        //Wrap around the top of the buffer
16728        for _ in 0..2 {
16729            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16730        }
16731    });
16732
16733    cx.assert_editor_state(
16734        &r#"
16735        use some::modified;
16736
16737
16738        fn main() {
16739        ˇ    println!("hello there");
16740
16741            println!("around the");
16742            println!("world");
16743        }
16744        "#
16745        .unindent(),
16746    );
16747
16748    cx.update_editor(|editor, window, cx| {
16749        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16750    });
16751
16752    cx.assert_editor_state(
16753        &r#"
16754        use some::modified;
16755
16756        ˇ
16757        fn main() {
16758            println!("hello there");
16759
16760            println!("around the");
16761            println!("world");
16762        }
16763        "#
16764        .unindent(),
16765    );
16766
16767    cx.update_editor(|editor, window, cx| {
16768        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16769    });
16770
16771    cx.assert_editor_state(
16772        &r#"
16773        ˇuse some::modified;
16774
16775
16776        fn main() {
16777            println!("hello there");
16778
16779            println!("around the");
16780            println!("world");
16781        }
16782        "#
16783        .unindent(),
16784    );
16785
16786    cx.update_editor(|editor, window, cx| {
16787        for _ in 0..2 {
16788            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16789        }
16790    });
16791
16792    cx.assert_editor_state(
16793        &r#"
16794        use some::modified;
16795
16796
16797        fn main() {
16798        ˇ    println!("hello there");
16799
16800            println!("around the");
16801            println!("world");
16802        }
16803        "#
16804        .unindent(),
16805    );
16806
16807    cx.update_editor(|editor, window, cx| {
16808        editor.fold(&Fold, window, cx);
16809    });
16810
16811    cx.update_editor(|editor, window, cx| {
16812        editor.go_to_next_hunk(&GoToHunk, window, cx);
16813    });
16814
16815    cx.assert_editor_state(
16816        &r#"
16817        ˇuse some::modified;
16818
16819
16820        fn main() {
16821            println!("hello there");
16822
16823            println!("around the");
16824            println!("world");
16825        }
16826        "#
16827        .unindent(),
16828    );
16829}
16830
16831#[test]
16832fn test_split_words() {
16833    fn split(text: &str) -> Vec<&str> {
16834        split_words(text).collect()
16835    }
16836
16837    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
16838    assert_eq!(split("hello_world"), &["hello_", "world"]);
16839    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
16840    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
16841    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
16842    assert_eq!(split("helloworld"), &["helloworld"]);
16843
16844    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
16845}
16846
16847#[gpui::test]
16848async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
16849    init_test(cx, |_| {});
16850
16851    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
16852    let mut assert = |before, after| {
16853        let _state_context = cx.set_state(before);
16854        cx.run_until_parked();
16855        cx.update_editor(|editor, window, cx| {
16856            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
16857        });
16858        cx.run_until_parked();
16859        cx.assert_editor_state(after);
16860    };
16861
16862    // Outside bracket jumps to outside of matching bracket
16863    assert("console.logˇ(var);", "console.log(var)ˇ;");
16864    assert("console.log(var)ˇ;", "console.logˇ(var);");
16865
16866    // Inside bracket jumps to inside of matching bracket
16867    assert("console.log(ˇvar);", "console.log(varˇ);");
16868    assert("console.log(varˇ);", "console.log(ˇvar);");
16869
16870    // When outside a bracket and inside, favor jumping to the inside bracket
16871    assert(
16872        "console.log('foo', [1, 2, 3]ˇ);",
16873        "console.log(ˇ'foo', [1, 2, 3]);",
16874    );
16875    assert(
16876        "console.log(ˇ'foo', [1, 2, 3]);",
16877        "console.log('foo', [1, 2, 3]ˇ);",
16878    );
16879
16880    // Bias forward if two options are equally likely
16881    assert(
16882        "let result = curried_fun()ˇ();",
16883        "let result = curried_fun()()ˇ;",
16884    );
16885
16886    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
16887    assert(
16888        indoc! {"
16889            function test() {
16890                console.log('test')ˇ
16891            }"},
16892        indoc! {"
16893            function test() {
16894                console.logˇ('test')
16895            }"},
16896    );
16897}
16898
16899#[gpui::test]
16900async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
16901    init_test(cx, |_| {});
16902
16903    let fs = FakeFs::new(cx.executor());
16904    fs.insert_tree(
16905        path!("/a"),
16906        json!({
16907            "main.rs": "fn main() { let a = 5; }",
16908            "other.rs": "// Test file",
16909        }),
16910    )
16911    .await;
16912    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16913
16914    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16915    language_registry.add(Arc::new(Language::new(
16916        LanguageConfig {
16917            name: "Rust".into(),
16918            matcher: LanguageMatcher {
16919                path_suffixes: vec!["rs".to_string()],
16920                ..Default::default()
16921            },
16922            brackets: BracketPairConfig {
16923                pairs: vec![BracketPair {
16924                    start: "{".to_string(),
16925                    end: "}".to_string(),
16926                    close: true,
16927                    surround: true,
16928                    newline: true,
16929                }],
16930                disabled_scopes_by_bracket_ix: Vec::new(),
16931            },
16932            ..Default::default()
16933        },
16934        Some(tree_sitter_rust::LANGUAGE.into()),
16935    )));
16936    let mut fake_servers = language_registry.register_fake_lsp(
16937        "Rust",
16938        FakeLspAdapter {
16939            capabilities: lsp::ServerCapabilities {
16940                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
16941                    first_trigger_character: "{".to_string(),
16942                    more_trigger_character: None,
16943                }),
16944                ..Default::default()
16945            },
16946            ..Default::default()
16947        },
16948    );
16949
16950    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16951
16952    let cx = &mut VisualTestContext::from_window(*workspace, cx);
16953
16954    let worktree_id = workspace
16955        .update(cx, |workspace, _, cx| {
16956            workspace.project().update(cx, |project, cx| {
16957                project.worktrees(cx).next().unwrap().read(cx).id()
16958            })
16959        })
16960        .unwrap();
16961
16962    let buffer = project
16963        .update(cx, |project, cx| {
16964            project.open_local_buffer(path!("/a/main.rs"), cx)
16965        })
16966        .await
16967        .unwrap();
16968    let editor_handle = workspace
16969        .update(cx, |workspace, window, cx| {
16970            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
16971        })
16972        .unwrap()
16973        .await
16974        .unwrap()
16975        .downcast::<Editor>()
16976        .unwrap();
16977
16978    cx.executor().start_waiting();
16979    let fake_server = fake_servers.next().await.unwrap();
16980
16981    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
16982        |params, _| async move {
16983            assert_eq!(
16984                params.text_document_position.text_document.uri,
16985                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
16986            );
16987            assert_eq!(
16988                params.text_document_position.position,
16989                lsp::Position::new(0, 21),
16990            );
16991
16992            Ok(Some(vec![lsp::TextEdit {
16993                new_text: "]".to_string(),
16994                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16995            }]))
16996        },
16997    );
16998
16999    editor_handle.update_in(cx, |editor, window, cx| {
17000        window.focus(&editor.focus_handle(cx));
17001        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17002            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
17003        });
17004        editor.handle_input("{", window, cx);
17005    });
17006
17007    cx.executor().run_until_parked();
17008
17009    buffer.update(cx, |buffer, _| {
17010        assert_eq!(
17011            buffer.text(),
17012            "fn main() { let a = {5}; }",
17013            "No extra braces from on type formatting should appear in the buffer"
17014        )
17015    });
17016}
17017
17018#[gpui::test(iterations = 20, seeds(31))]
17019async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
17020    init_test(cx, |_| {});
17021
17022    let mut cx = EditorLspTestContext::new_rust(
17023        lsp::ServerCapabilities {
17024            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17025                first_trigger_character: ".".to_string(),
17026                more_trigger_character: None,
17027            }),
17028            ..Default::default()
17029        },
17030        cx,
17031    )
17032    .await;
17033
17034    cx.update_buffer(|buffer, _| {
17035        // This causes autoindent to be async.
17036        buffer.set_sync_parse_timeout(Duration::ZERO)
17037    });
17038
17039    cx.set_state("fn c() {\n    d()ˇ\n}\n");
17040    cx.simulate_keystroke("\n");
17041    cx.run_until_parked();
17042
17043    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
17044    let mut request =
17045        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
17046            let buffer_cloned = buffer_cloned.clone();
17047            async move {
17048                buffer_cloned.update(&mut cx, |buffer, _| {
17049                    assert_eq!(
17050                        buffer.text(),
17051                        "fn c() {\n    d()\n        .\n}\n",
17052                        "OnTypeFormatting should triggered after autoindent applied"
17053                    )
17054                })?;
17055
17056                Ok(Some(vec![]))
17057            }
17058        });
17059
17060    cx.simulate_keystroke(".");
17061    cx.run_until_parked();
17062
17063    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
17064    assert!(request.next().await.is_some());
17065    request.close();
17066    assert!(request.next().await.is_none());
17067}
17068
17069#[gpui::test]
17070async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17071    init_test(cx, |_| {});
17072
17073    let fs = FakeFs::new(cx.executor());
17074    fs.insert_tree(
17075        path!("/a"),
17076        json!({
17077            "main.rs": "fn main() { let a = 5; }",
17078            "other.rs": "// Test file",
17079        }),
17080    )
17081    .await;
17082
17083    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17084
17085    let server_restarts = Arc::new(AtomicUsize::new(0));
17086    let closure_restarts = Arc::clone(&server_restarts);
17087    let language_server_name = "test language server";
17088    let language_name: LanguageName = "Rust".into();
17089
17090    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17091    language_registry.add(Arc::new(Language::new(
17092        LanguageConfig {
17093            name: language_name.clone(),
17094            matcher: LanguageMatcher {
17095                path_suffixes: vec!["rs".to_string()],
17096                ..Default::default()
17097            },
17098            ..Default::default()
17099        },
17100        Some(tree_sitter_rust::LANGUAGE.into()),
17101    )));
17102    let mut fake_servers = language_registry.register_fake_lsp(
17103        "Rust",
17104        FakeLspAdapter {
17105            name: language_server_name,
17106            initialization_options: Some(json!({
17107                "testOptionValue": true
17108            })),
17109            initializer: Some(Box::new(move |fake_server| {
17110                let task_restarts = Arc::clone(&closure_restarts);
17111                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17112                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17113                    futures::future::ready(Ok(()))
17114                });
17115            })),
17116            ..Default::default()
17117        },
17118    );
17119
17120    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17121    let _buffer = project
17122        .update(cx, |project, cx| {
17123            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17124        })
17125        .await
17126        .unwrap();
17127    let _fake_server = fake_servers.next().await.unwrap();
17128    update_test_language_settings(cx, |language_settings| {
17129        language_settings.languages.0.insert(
17130            language_name.clone().0,
17131            LanguageSettingsContent {
17132                tab_size: NonZeroU32::new(8),
17133                ..Default::default()
17134            },
17135        );
17136    });
17137    cx.executor().run_until_parked();
17138    assert_eq!(
17139        server_restarts.load(atomic::Ordering::Acquire),
17140        0,
17141        "Should not restart LSP server on an unrelated change"
17142    );
17143
17144    update_test_project_settings(cx, |project_settings| {
17145        project_settings.lsp.insert(
17146            "Some other server name".into(),
17147            LspSettings {
17148                binary: None,
17149                settings: None,
17150                initialization_options: Some(json!({
17151                    "some other init value": false
17152                })),
17153                enable_lsp_tasks: false,
17154                fetch: None,
17155            },
17156        );
17157    });
17158    cx.executor().run_until_parked();
17159    assert_eq!(
17160        server_restarts.load(atomic::Ordering::Acquire),
17161        0,
17162        "Should not restart LSP server on an unrelated LSP settings change"
17163    );
17164
17165    update_test_project_settings(cx, |project_settings| {
17166        project_settings.lsp.insert(
17167            language_server_name.into(),
17168            LspSettings {
17169                binary: None,
17170                settings: None,
17171                initialization_options: Some(json!({
17172                    "anotherInitValue": false
17173                })),
17174                enable_lsp_tasks: false,
17175                fetch: None,
17176            },
17177        );
17178    });
17179    cx.executor().run_until_parked();
17180    assert_eq!(
17181        server_restarts.load(atomic::Ordering::Acquire),
17182        1,
17183        "Should restart LSP server on a related LSP settings change"
17184    );
17185
17186    update_test_project_settings(cx, |project_settings| {
17187        project_settings.lsp.insert(
17188            language_server_name.into(),
17189            LspSettings {
17190                binary: None,
17191                settings: None,
17192                initialization_options: Some(json!({
17193                    "anotherInitValue": false
17194                })),
17195                enable_lsp_tasks: false,
17196                fetch: None,
17197            },
17198        );
17199    });
17200    cx.executor().run_until_parked();
17201    assert_eq!(
17202        server_restarts.load(atomic::Ordering::Acquire),
17203        1,
17204        "Should not restart LSP server on a related LSP settings change that is the same"
17205    );
17206
17207    update_test_project_settings(cx, |project_settings| {
17208        project_settings.lsp.insert(
17209            language_server_name.into(),
17210            LspSettings {
17211                binary: None,
17212                settings: None,
17213                initialization_options: None,
17214                enable_lsp_tasks: false,
17215                fetch: None,
17216            },
17217        );
17218    });
17219    cx.executor().run_until_parked();
17220    assert_eq!(
17221        server_restarts.load(atomic::Ordering::Acquire),
17222        2,
17223        "Should restart LSP server on another related LSP settings change"
17224    );
17225}
17226
17227#[gpui::test]
17228async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17229    init_test(cx, |_| {});
17230
17231    let mut cx = EditorLspTestContext::new_rust(
17232        lsp::ServerCapabilities {
17233            completion_provider: Some(lsp::CompletionOptions {
17234                trigger_characters: Some(vec![".".to_string()]),
17235                resolve_provider: Some(true),
17236                ..Default::default()
17237            }),
17238            ..Default::default()
17239        },
17240        cx,
17241    )
17242    .await;
17243
17244    cx.set_state("fn main() { let a = 2ˇ; }");
17245    cx.simulate_keystroke(".");
17246    let completion_item = lsp::CompletionItem {
17247        label: "some".into(),
17248        kind: Some(lsp::CompletionItemKind::SNIPPET),
17249        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17250        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17251            kind: lsp::MarkupKind::Markdown,
17252            value: "```rust\nSome(2)\n```".to_string(),
17253        })),
17254        deprecated: Some(false),
17255        sort_text: Some("fffffff2".to_string()),
17256        filter_text: Some("some".to_string()),
17257        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17258        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17259            range: lsp::Range {
17260                start: lsp::Position {
17261                    line: 0,
17262                    character: 22,
17263                },
17264                end: lsp::Position {
17265                    line: 0,
17266                    character: 22,
17267                },
17268            },
17269            new_text: "Some(2)".to_string(),
17270        })),
17271        additional_text_edits: Some(vec![lsp::TextEdit {
17272            range: lsp::Range {
17273                start: lsp::Position {
17274                    line: 0,
17275                    character: 20,
17276                },
17277                end: lsp::Position {
17278                    line: 0,
17279                    character: 22,
17280                },
17281            },
17282            new_text: "".to_string(),
17283        }]),
17284        ..Default::default()
17285    };
17286
17287    let closure_completion_item = completion_item.clone();
17288    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17289        let task_completion_item = closure_completion_item.clone();
17290        async move {
17291            Ok(Some(lsp::CompletionResponse::Array(vec![
17292                task_completion_item,
17293            ])))
17294        }
17295    });
17296
17297    request.next().await;
17298
17299    cx.condition(|editor, _| editor.context_menu_visible())
17300        .await;
17301    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17302        editor
17303            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17304            .unwrap()
17305    });
17306    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17307
17308    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17309        let task_completion_item = completion_item.clone();
17310        async move { Ok(task_completion_item) }
17311    })
17312    .next()
17313    .await
17314    .unwrap();
17315    apply_additional_edits.await.unwrap();
17316    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17317}
17318
17319#[gpui::test]
17320async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17321    init_test(cx, |_| {});
17322
17323    let mut cx = EditorLspTestContext::new_rust(
17324        lsp::ServerCapabilities {
17325            completion_provider: Some(lsp::CompletionOptions {
17326                trigger_characters: Some(vec![".".to_string()]),
17327                resolve_provider: Some(true),
17328                ..Default::default()
17329            }),
17330            ..Default::default()
17331        },
17332        cx,
17333    )
17334    .await;
17335
17336    cx.set_state("fn main() { let a = 2ˇ; }");
17337    cx.simulate_keystroke(".");
17338
17339    let item1 = lsp::CompletionItem {
17340        label: "method id()".to_string(),
17341        filter_text: Some("id".to_string()),
17342        detail: None,
17343        documentation: None,
17344        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17345            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17346            new_text: ".id".to_string(),
17347        })),
17348        ..lsp::CompletionItem::default()
17349    };
17350
17351    let item2 = lsp::CompletionItem {
17352        label: "other".to_string(),
17353        filter_text: Some("other".to_string()),
17354        detail: None,
17355        documentation: None,
17356        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17357            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17358            new_text: ".other".to_string(),
17359        })),
17360        ..lsp::CompletionItem::default()
17361    };
17362
17363    let item1 = item1.clone();
17364    cx.set_request_handler::<lsp::request::Completion, _, _>({
17365        let item1 = item1.clone();
17366        move |_, _, _| {
17367            let item1 = item1.clone();
17368            let item2 = item2.clone();
17369            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17370        }
17371    })
17372    .next()
17373    .await;
17374
17375    cx.condition(|editor, _| editor.context_menu_visible())
17376        .await;
17377    cx.update_editor(|editor, _, _| {
17378        let context_menu = editor.context_menu.borrow_mut();
17379        let context_menu = context_menu
17380            .as_ref()
17381            .expect("Should have the context menu deployed");
17382        match context_menu {
17383            CodeContextMenu::Completions(completions_menu) => {
17384                let completions = completions_menu.completions.borrow_mut();
17385                assert_eq!(
17386                    completions
17387                        .iter()
17388                        .map(|completion| &completion.label.text)
17389                        .collect::<Vec<_>>(),
17390                    vec!["method id()", "other"]
17391                )
17392            }
17393            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17394        }
17395    });
17396
17397    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17398        let item1 = item1.clone();
17399        move |_, item_to_resolve, _| {
17400            let item1 = item1.clone();
17401            async move {
17402                if item1 == item_to_resolve {
17403                    Ok(lsp::CompletionItem {
17404                        label: "method id()".to_string(),
17405                        filter_text: Some("id".to_string()),
17406                        detail: Some("Now resolved!".to_string()),
17407                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17408                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17409                            range: lsp::Range::new(
17410                                lsp::Position::new(0, 22),
17411                                lsp::Position::new(0, 22),
17412                            ),
17413                            new_text: ".id".to_string(),
17414                        })),
17415                        ..lsp::CompletionItem::default()
17416                    })
17417                } else {
17418                    Ok(item_to_resolve)
17419                }
17420            }
17421        }
17422    })
17423    .next()
17424    .await
17425    .unwrap();
17426    cx.run_until_parked();
17427
17428    cx.update_editor(|editor, window, cx| {
17429        editor.context_menu_next(&Default::default(), window, cx);
17430    });
17431
17432    cx.update_editor(|editor, _, _| {
17433        let context_menu = editor.context_menu.borrow_mut();
17434        let context_menu = context_menu
17435            .as_ref()
17436            .expect("Should have the context menu deployed");
17437        match context_menu {
17438            CodeContextMenu::Completions(completions_menu) => {
17439                let completions = completions_menu.completions.borrow_mut();
17440                assert_eq!(
17441                    completions
17442                        .iter()
17443                        .map(|completion| &completion.label.text)
17444                        .collect::<Vec<_>>(),
17445                    vec!["method id() Now resolved!", "other"],
17446                    "Should update first completion label, but not second as the filter text did not match."
17447                );
17448            }
17449            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17450        }
17451    });
17452}
17453
17454#[gpui::test]
17455async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17456    init_test(cx, |_| {});
17457    let mut cx = EditorLspTestContext::new_rust(
17458        lsp::ServerCapabilities {
17459            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17460            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17461            completion_provider: Some(lsp::CompletionOptions {
17462                resolve_provider: Some(true),
17463                ..Default::default()
17464            }),
17465            ..Default::default()
17466        },
17467        cx,
17468    )
17469    .await;
17470    cx.set_state(indoc! {"
17471        struct TestStruct {
17472            field: i32
17473        }
17474
17475        fn mainˇ() {
17476            let unused_var = 42;
17477            let test_struct = TestStruct { field: 42 };
17478        }
17479    "});
17480    let symbol_range = cx.lsp_range(indoc! {"
17481        struct TestStruct {
17482            field: i32
17483        }
17484
17485        «fn main»() {
17486            let unused_var = 42;
17487            let test_struct = TestStruct { field: 42 };
17488        }
17489    "});
17490    let mut hover_requests =
17491        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17492            Ok(Some(lsp::Hover {
17493                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17494                    kind: lsp::MarkupKind::Markdown,
17495                    value: "Function documentation".to_string(),
17496                }),
17497                range: Some(symbol_range),
17498            }))
17499        });
17500
17501    // Case 1: Test that code action menu hide hover popover
17502    cx.dispatch_action(Hover);
17503    hover_requests.next().await;
17504    cx.condition(|editor, _| editor.hover_state.visible()).await;
17505    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17506        move |_, _, _| async move {
17507            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17508                lsp::CodeAction {
17509                    title: "Remove unused variable".to_string(),
17510                    kind: Some(CodeActionKind::QUICKFIX),
17511                    edit: Some(lsp::WorkspaceEdit {
17512                        changes: Some(
17513                            [(
17514                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17515                                vec![lsp::TextEdit {
17516                                    range: lsp::Range::new(
17517                                        lsp::Position::new(5, 4),
17518                                        lsp::Position::new(5, 27),
17519                                    ),
17520                                    new_text: "".to_string(),
17521                                }],
17522                            )]
17523                            .into_iter()
17524                            .collect(),
17525                        ),
17526                        ..Default::default()
17527                    }),
17528                    ..Default::default()
17529                },
17530            )]))
17531        },
17532    );
17533    cx.update_editor(|editor, window, cx| {
17534        editor.toggle_code_actions(
17535            &ToggleCodeActions {
17536                deployed_from: None,
17537                quick_launch: false,
17538            },
17539            window,
17540            cx,
17541        );
17542    });
17543    code_action_requests.next().await;
17544    cx.run_until_parked();
17545    cx.condition(|editor, _| editor.context_menu_visible())
17546        .await;
17547    cx.update_editor(|editor, _, _| {
17548        assert!(
17549            !editor.hover_state.visible(),
17550            "Hover popover should be hidden when code action menu is shown"
17551        );
17552        // Hide code actions
17553        editor.context_menu.take();
17554    });
17555
17556    // Case 2: Test that code completions hide hover popover
17557    cx.dispatch_action(Hover);
17558    hover_requests.next().await;
17559    cx.condition(|editor, _| editor.hover_state.visible()).await;
17560    let counter = Arc::new(AtomicUsize::new(0));
17561    let mut completion_requests =
17562        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17563            let counter = counter.clone();
17564            async move {
17565                counter.fetch_add(1, atomic::Ordering::Release);
17566                Ok(Some(lsp::CompletionResponse::Array(vec![
17567                    lsp::CompletionItem {
17568                        label: "main".into(),
17569                        kind: Some(lsp::CompletionItemKind::FUNCTION),
17570                        detail: Some("() -> ()".to_string()),
17571                        ..Default::default()
17572                    },
17573                    lsp::CompletionItem {
17574                        label: "TestStruct".into(),
17575                        kind: Some(lsp::CompletionItemKind::STRUCT),
17576                        detail: Some("struct TestStruct".to_string()),
17577                        ..Default::default()
17578                    },
17579                ])))
17580            }
17581        });
17582    cx.update_editor(|editor, window, cx| {
17583        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
17584    });
17585    completion_requests.next().await;
17586    cx.condition(|editor, _| editor.context_menu_visible())
17587        .await;
17588    cx.update_editor(|editor, _, _| {
17589        assert!(
17590            !editor.hover_state.visible(),
17591            "Hover popover should be hidden when completion menu is shown"
17592        );
17593    });
17594}
17595
17596#[gpui::test]
17597async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
17598    init_test(cx, |_| {});
17599
17600    let mut cx = EditorLspTestContext::new_rust(
17601        lsp::ServerCapabilities {
17602            completion_provider: Some(lsp::CompletionOptions {
17603                trigger_characters: Some(vec![".".to_string()]),
17604                resolve_provider: Some(true),
17605                ..Default::default()
17606            }),
17607            ..Default::default()
17608        },
17609        cx,
17610    )
17611    .await;
17612
17613    cx.set_state("fn main() { let a = 2ˇ; }");
17614    cx.simulate_keystroke(".");
17615
17616    let unresolved_item_1 = lsp::CompletionItem {
17617        label: "id".to_string(),
17618        filter_text: Some("id".to_string()),
17619        detail: None,
17620        documentation: None,
17621        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17622            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17623            new_text: ".id".to_string(),
17624        })),
17625        ..lsp::CompletionItem::default()
17626    };
17627    let resolved_item_1 = lsp::CompletionItem {
17628        additional_text_edits: Some(vec![lsp::TextEdit {
17629            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17630            new_text: "!!".to_string(),
17631        }]),
17632        ..unresolved_item_1.clone()
17633    };
17634    let unresolved_item_2 = lsp::CompletionItem {
17635        label: "other".to_string(),
17636        filter_text: Some("other".to_string()),
17637        detail: None,
17638        documentation: None,
17639        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17640            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17641            new_text: ".other".to_string(),
17642        })),
17643        ..lsp::CompletionItem::default()
17644    };
17645    let resolved_item_2 = lsp::CompletionItem {
17646        additional_text_edits: Some(vec![lsp::TextEdit {
17647            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17648            new_text: "??".to_string(),
17649        }]),
17650        ..unresolved_item_2.clone()
17651    };
17652
17653    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
17654    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
17655    cx.lsp
17656        .server
17657        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17658            let unresolved_item_1 = unresolved_item_1.clone();
17659            let resolved_item_1 = resolved_item_1.clone();
17660            let unresolved_item_2 = unresolved_item_2.clone();
17661            let resolved_item_2 = resolved_item_2.clone();
17662            let resolve_requests_1 = resolve_requests_1.clone();
17663            let resolve_requests_2 = resolve_requests_2.clone();
17664            move |unresolved_request, _| {
17665                let unresolved_item_1 = unresolved_item_1.clone();
17666                let resolved_item_1 = resolved_item_1.clone();
17667                let unresolved_item_2 = unresolved_item_2.clone();
17668                let resolved_item_2 = resolved_item_2.clone();
17669                let resolve_requests_1 = resolve_requests_1.clone();
17670                let resolve_requests_2 = resolve_requests_2.clone();
17671                async move {
17672                    if unresolved_request == unresolved_item_1 {
17673                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
17674                        Ok(resolved_item_1.clone())
17675                    } else if unresolved_request == unresolved_item_2 {
17676                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
17677                        Ok(resolved_item_2.clone())
17678                    } else {
17679                        panic!("Unexpected completion item {unresolved_request:?}")
17680                    }
17681                }
17682            }
17683        })
17684        .detach();
17685
17686    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17687        let unresolved_item_1 = unresolved_item_1.clone();
17688        let unresolved_item_2 = unresolved_item_2.clone();
17689        async move {
17690            Ok(Some(lsp::CompletionResponse::Array(vec![
17691                unresolved_item_1,
17692                unresolved_item_2,
17693            ])))
17694        }
17695    })
17696    .next()
17697    .await;
17698
17699    cx.condition(|editor, _| editor.context_menu_visible())
17700        .await;
17701    cx.update_editor(|editor, _, _| {
17702        let context_menu = editor.context_menu.borrow_mut();
17703        let context_menu = context_menu
17704            .as_ref()
17705            .expect("Should have the context menu deployed");
17706        match context_menu {
17707            CodeContextMenu::Completions(completions_menu) => {
17708                let completions = completions_menu.completions.borrow_mut();
17709                assert_eq!(
17710                    completions
17711                        .iter()
17712                        .map(|completion| &completion.label.text)
17713                        .collect::<Vec<_>>(),
17714                    vec!["id", "other"]
17715                )
17716            }
17717            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17718        }
17719    });
17720    cx.run_until_parked();
17721
17722    cx.update_editor(|editor, window, cx| {
17723        editor.context_menu_next(&ContextMenuNext, window, cx);
17724    });
17725    cx.run_until_parked();
17726    cx.update_editor(|editor, window, cx| {
17727        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17728    });
17729    cx.run_until_parked();
17730    cx.update_editor(|editor, window, cx| {
17731        editor.context_menu_next(&ContextMenuNext, window, cx);
17732    });
17733    cx.run_until_parked();
17734    cx.update_editor(|editor, window, cx| {
17735        editor
17736            .compose_completion(&ComposeCompletion::default(), window, cx)
17737            .expect("No task returned")
17738    })
17739    .await
17740    .expect("Completion failed");
17741    cx.run_until_parked();
17742
17743    cx.update_editor(|editor, _, cx| {
17744        assert_eq!(
17745            resolve_requests_1.load(atomic::Ordering::Acquire),
17746            1,
17747            "Should always resolve once despite multiple selections"
17748        );
17749        assert_eq!(
17750            resolve_requests_2.load(atomic::Ordering::Acquire),
17751            1,
17752            "Should always resolve once after multiple selections and applying the completion"
17753        );
17754        assert_eq!(
17755            editor.text(cx),
17756            "fn main() { let a = ??.other; }",
17757            "Should use resolved data when applying the completion"
17758        );
17759    });
17760}
17761
17762#[gpui::test]
17763async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
17764    init_test(cx, |_| {});
17765
17766    let item_0 = lsp::CompletionItem {
17767        label: "abs".into(),
17768        insert_text: Some("abs".into()),
17769        data: Some(json!({ "very": "special"})),
17770        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
17771        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
17772            lsp::InsertReplaceEdit {
17773                new_text: "abs".to_string(),
17774                insert: lsp::Range::default(),
17775                replace: lsp::Range::default(),
17776            },
17777        )),
17778        ..lsp::CompletionItem::default()
17779    };
17780    let items = iter::once(item_0.clone())
17781        .chain((11..51).map(|i| lsp::CompletionItem {
17782            label: format!("item_{}", i),
17783            insert_text: Some(format!("item_{}", i)),
17784            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
17785            ..lsp::CompletionItem::default()
17786        }))
17787        .collect::<Vec<_>>();
17788
17789    let default_commit_characters = vec!["?".to_string()];
17790    let default_data = json!({ "default": "data"});
17791    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
17792    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
17793    let default_edit_range = lsp::Range {
17794        start: lsp::Position {
17795            line: 0,
17796            character: 5,
17797        },
17798        end: lsp::Position {
17799            line: 0,
17800            character: 5,
17801        },
17802    };
17803
17804    let mut cx = EditorLspTestContext::new_rust(
17805        lsp::ServerCapabilities {
17806            completion_provider: Some(lsp::CompletionOptions {
17807                trigger_characters: Some(vec![".".to_string()]),
17808                resolve_provider: Some(true),
17809                ..Default::default()
17810            }),
17811            ..Default::default()
17812        },
17813        cx,
17814    )
17815    .await;
17816
17817    cx.set_state("fn main() { let a = 2ˇ; }");
17818    cx.simulate_keystroke(".");
17819
17820    let completion_data = default_data.clone();
17821    let completion_characters = default_commit_characters.clone();
17822    let completion_items = items.clone();
17823    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17824        let default_data = completion_data.clone();
17825        let default_commit_characters = completion_characters.clone();
17826        let items = completion_items.clone();
17827        async move {
17828            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
17829                items,
17830                item_defaults: Some(lsp::CompletionListItemDefaults {
17831                    data: Some(default_data.clone()),
17832                    commit_characters: Some(default_commit_characters.clone()),
17833                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
17834                        default_edit_range,
17835                    )),
17836                    insert_text_format: Some(default_insert_text_format),
17837                    insert_text_mode: Some(default_insert_text_mode),
17838                }),
17839                ..lsp::CompletionList::default()
17840            })))
17841        }
17842    })
17843    .next()
17844    .await;
17845
17846    let resolved_items = Arc::new(Mutex::new(Vec::new()));
17847    cx.lsp
17848        .server
17849        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17850            let closure_resolved_items = resolved_items.clone();
17851            move |item_to_resolve, _| {
17852                let closure_resolved_items = closure_resolved_items.clone();
17853                async move {
17854                    closure_resolved_items.lock().push(item_to_resolve.clone());
17855                    Ok(item_to_resolve)
17856                }
17857            }
17858        })
17859        .detach();
17860
17861    cx.condition(|editor, _| editor.context_menu_visible())
17862        .await;
17863    cx.run_until_parked();
17864    cx.update_editor(|editor, _, _| {
17865        let menu = editor.context_menu.borrow_mut();
17866        match menu.as_ref().expect("should have the completions menu") {
17867            CodeContextMenu::Completions(completions_menu) => {
17868                assert_eq!(
17869                    completions_menu
17870                        .entries
17871                        .borrow()
17872                        .iter()
17873                        .map(|mat| mat.string.clone())
17874                        .collect::<Vec<String>>(),
17875                    items
17876                        .iter()
17877                        .map(|completion| completion.label.clone())
17878                        .collect::<Vec<String>>()
17879                );
17880            }
17881            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
17882        }
17883    });
17884    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
17885    // with 4 from the end.
17886    assert_eq!(
17887        *resolved_items.lock(),
17888        [&items[0..16], &items[items.len() - 4..items.len()]]
17889            .concat()
17890            .iter()
17891            .cloned()
17892            .map(|mut item| {
17893                if item.data.is_none() {
17894                    item.data = Some(default_data.clone());
17895                }
17896                item
17897            })
17898            .collect::<Vec<lsp::CompletionItem>>(),
17899        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
17900    );
17901    resolved_items.lock().clear();
17902
17903    cx.update_editor(|editor, window, cx| {
17904        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17905    });
17906    cx.run_until_parked();
17907    // Completions that have already been resolved are skipped.
17908    assert_eq!(
17909        *resolved_items.lock(),
17910        items[items.len() - 17..items.len() - 4]
17911            .iter()
17912            .cloned()
17913            .map(|mut item| {
17914                if item.data.is_none() {
17915                    item.data = Some(default_data.clone());
17916                }
17917                item
17918            })
17919            .collect::<Vec<lsp::CompletionItem>>()
17920    );
17921    resolved_items.lock().clear();
17922}
17923
17924#[gpui::test]
17925async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
17926    init_test(cx, |_| {});
17927
17928    let mut cx = EditorLspTestContext::new(
17929        Language::new(
17930            LanguageConfig {
17931                matcher: LanguageMatcher {
17932                    path_suffixes: vec!["jsx".into()],
17933                    ..Default::default()
17934                },
17935                overrides: [(
17936                    "element".into(),
17937                    LanguageConfigOverride {
17938                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
17939                        ..Default::default()
17940                    },
17941                )]
17942                .into_iter()
17943                .collect(),
17944                ..Default::default()
17945            },
17946            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
17947        )
17948        .with_override_query("(jsx_self_closing_element) @element")
17949        .unwrap(),
17950        lsp::ServerCapabilities {
17951            completion_provider: Some(lsp::CompletionOptions {
17952                trigger_characters: Some(vec![":".to_string()]),
17953                ..Default::default()
17954            }),
17955            ..Default::default()
17956        },
17957        cx,
17958    )
17959    .await;
17960
17961    cx.lsp
17962        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
17963            Ok(Some(lsp::CompletionResponse::Array(vec![
17964                lsp::CompletionItem {
17965                    label: "bg-blue".into(),
17966                    ..Default::default()
17967                },
17968                lsp::CompletionItem {
17969                    label: "bg-red".into(),
17970                    ..Default::default()
17971                },
17972                lsp::CompletionItem {
17973                    label: "bg-yellow".into(),
17974                    ..Default::default()
17975                },
17976            ])))
17977        });
17978
17979    cx.set_state(r#"<p class="bgˇ" />"#);
17980
17981    // Trigger completion when typing a dash, because the dash is an extra
17982    // word character in the 'element' scope, which contains the cursor.
17983    cx.simulate_keystroke("-");
17984    cx.executor().run_until_parked();
17985    cx.update_editor(|editor, _, _| {
17986        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17987        {
17988            assert_eq!(
17989                completion_menu_entries(menu),
17990                &["bg-blue", "bg-red", "bg-yellow"]
17991            );
17992        } else {
17993            panic!("expected completion menu to be open");
17994        }
17995    });
17996
17997    cx.simulate_keystroke("l");
17998    cx.executor().run_until_parked();
17999    cx.update_editor(|editor, _, _| {
18000        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18001        {
18002            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
18003        } else {
18004            panic!("expected completion menu to be open");
18005        }
18006    });
18007
18008    // When filtering completions, consider the character after the '-' to
18009    // be the start of a subword.
18010    cx.set_state(r#"<p class="yelˇ" />"#);
18011    cx.simulate_keystroke("l");
18012    cx.executor().run_until_parked();
18013    cx.update_editor(|editor, _, _| {
18014        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18015        {
18016            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
18017        } else {
18018            panic!("expected completion menu to be open");
18019        }
18020    });
18021}
18022
18023fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
18024    let entries = menu.entries.borrow();
18025    entries.iter().map(|mat| mat.string.clone()).collect()
18026}
18027
18028#[gpui::test]
18029async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
18030    init_test(cx, |settings| {
18031        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
18032            Formatter::Prettier,
18033        )))
18034    });
18035
18036    let fs = FakeFs::new(cx.executor());
18037    fs.insert_file(path!("/file.ts"), Default::default()).await;
18038
18039    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
18040    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18041
18042    language_registry.add(Arc::new(Language::new(
18043        LanguageConfig {
18044            name: "TypeScript".into(),
18045            matcher: LanguageMatcher {
18046                path_suffixes: vec!["ts".to_string()],
18047                ..Default::default()
18048            },
18049            ..Default::default()
18050        },
18051        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18052    )));
18053    update_test_language_settings(cx, |settings| {
18054        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
18055    });
18056
18057    let test_plugin = "test_plugin";
18058    let _ = language_registry.register_fake_lsp(
18059        "TypeScript",
18060        FakeLspAdapter {
18061            prettier_plugins: vec![test_plugin],
18062            ..Default::default()
18063        },
18064    );
18065
18066    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
18067    let buffer = project
18068        .update(cx, |project, cx| {
18069            project.open_local_buffer(path!("/file.ts"), cx)
18070        })
18071        .await
18072        .unwrap();
18073
18074    let buffer_text = "one\ntwo\nthree\n";
18075    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18076    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18077    editor.update_in(cx, |editor, window, cx| {
18078        editor.set_text(buffer_text, window, cx)
18079    });
18080
18081    editor
18082        .update_in(cx, |editor, window, cx| {
18083            editor.perform_format(
18084                project.clone(),
18085                FormatTrigger::Manual,
18086                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18087                window,
18088                cx,
18089            )
18090        })
18091        .unwrap()
18092        .await;
18093    assert_eq!(
18094        editor.update(cx, |editor, cx| editor.text(cx)),
18095        buffer_text.to_string() + prettier_format_suffix,
18096        "Test prettier formatting was not applied to the original buffer text",
18097    );
18098
18099    update_test_language_settings(cx, |settings| {
18100        settings.defaults.formatter = Some(SelectedFormatter::Auto)
18101    });
18102    let format = editor.update_in(cx, |editor, window, cx| {
18103        editor.perform_format(
18104            project.clone(),
18105            FormatTrigger::Manual,
18106            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18107            window,
18108            cx,
18109        )
18110    });
18111    format.await.unwrap();
18112    assert_eq!(
18113        editor.update(cx, |editor, cx| editor.text(cx)),
18114        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18115        "Autoformatting (via test prettier) was not applied to the original buffer text",
18116    );
18117}
18118
18119#[gpui::test]
18120async fn test_addition_reverts(cx: &mut TestAppContext) {
18121    init_test(cx, |_| {});
18122    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18123    let base_text = indoc! {r#"
18124        struct Row;
18125        struct Row1;
18126        struct Row2;
18127
18128        struct Row4;
18129        struct Row5;
18130        struct Row6;
18131
18132        struct Row8;
18133        struct Row9;
18134        struct Row10;"#};
18135
18136    // When addition hunks are not adjacent to carets, no hunk revert is performed
18137    assert_hunk_revert(
18138        indoc! {r#"struct Row;
18139                   struct Row1;
18140                   struct Row1.1;
18141                   struct Row1.2;
18142                   struct Row2;ˇ
18143
18144                   struct Row4;
18145                   struct Row5;
18146                   struct Row6;
18147
18148                   struct Row8;
18149                   ˇstruct Row9;
18150                   struct Row9.1;
18151                   struct Row9.2;
18152                   struct Row9.3;
18153                   struct Row10;"#},
18154        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18155        indoc! {r#"struct Row;
18156                   struct Row1;
18157                   struct Row1.1;
18158                   struct Row1.2;
18159                   struct Row2;ˇ
18160
18161                   struct Row4;
18162                   struct Row5;
18163                   struct Row6;
18164
18165                   struct Row8;
18166                   ˇstruct Row9;
18167                   struct Row9.1;
18168                   struct Row9.2;
18169                   struct Row9.3;
18170                   struct Row10;"#},
18171        base_text,
18172        &mut cx,
18173    );
18174    // Same for selections
18175    assert_hunk_revert(
18176        indoc! {r#"struct Row;
18177                   struct Row1;
18178                   struct Row2;
18179                   struct Row2.1;
18180                   struct Row2.2;
18181                   «ˇ
18182                   struct Row4;
18183                   struct» Row5;
18184                   «struct Row6;
18185                   ˇ»
18186                   struct Row9.1;
18187                   struct Row9.2;
18188                   struct Row9.3;
18189                   struct Row8;
18190                   struct Row9;
18191                   struct Row10;"#},
18192        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18193        indoc! {r#"struct Row;
18194                   struct Row1;
18195                   struct Row2;
18196                   struct Row2.1;
18197                   struct Row2.2;
18198                   «ˇ
18199                   struct Row4;
18200                   struct» Row5;
18201                   «struct Row6;
18202                   ˇ»
18203                   struct Row9.1;
18204                   struct Row9.2;
18205                   struct Row9.3;
18206                   struct Row8;
18207                   struct Row9;
18208                   struct Row10;"#},
18209        base_text,
18210        &mut cx,
18211    );
18212
18213    // When carets and selections intersect the addition hunks, those are reverted.
18214    // Adjacent carets got merged.
18215    assert_hunk_revert(
18216        indoc! {r#"struct Row;
18217                   ˇ// something on the top
18218                   struct Row1;
18219                   struct Row2;
18220                   struct Roˇw3.1;
18221                   struct Row2.2;
18222                   struct Row2.3;ˇ
18223
18224                   struct Row4;
18225                   struct ˇRow5.1;
18226                   struct Row5.2;
18227                   struct «Rowˇ»5.3;
18228                   struct Row5;
18229                   struct Row6;
18230                   ˇ
18231                   struct Row9.1;
18232                   struct «Rowˇ»9.2;
18233                   struct «ˇRow»9.3;
18234                   struct Row8;
18235                   struct Row9;
18236                   «ˇ// something on bottom»
18237                   struct Row10;"#},
18238        vec![
18239            DiffHunkStatusKind::Added,
18240            DiffHunkStatusKind::Added,
18241            DiffHunkStatusKind::Added,
18242            DiffHunkStatusKind::Added,
18243            DiffHunkStatusKind::Added,
18244        ],
18245        indoc! {r#"struct Row;
18246                   ˇstruct Row1;
18247                   struct Row2;
18248                   ˇ
18249                   struct Row4;
18250                   ˇstruct Row5;
18251                   struct Row6;
18252                   ˇ
18253                   ˇstruct Row8;
18254                   struct Row9;
18255                   ˇstruct Row10;"#},
18256        base_text,
18257        &mut cx,
18258    );
18259}
18260
18261#[gpui::test]
18262async fn test_modification_reverts(cx: &mut TestAppContext) {
18263    init_test(cx, |_| {});
18264    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18265    let base_text = indoc! {r#"
18266        struct Row;
18267        struct Row1;
18268        struct Row2;
18269
18270        struct Row4;
18271        struct Row5;
18272        struct Row6;
18273
18274        struct Row8;
18275        struct Row9;
18276        struct Row10;"#};
18277
18278    // Modification hunks behave the same as the addition ones.
18279    assert_hunk_revert(
18280        indoc! {r#"struct Row;
18281                   struct Row1;
18282                   struct Row33;
18283                   ˇ
18284                   struct Row4;
18285                   struct Row5;
18286                   struct Row6;
18287                   ˇ
18288                   struct Row99;
18289                   struct Row9;
18290                   struct Row10;"#},
18291        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18292        indoc! {r#"struct Row;
18293                   struct Row1;
18294                   struct Row33;
18295                   ˇ
18296                   struct Row4;
18297                   struct Row5;
18298                   struct Row6;
18299                   ˇ
18300                   struct Row99;
18301                   struct Row9;
18302                   struct Row10;"#},
18303        base_text,
18304        &mut cx,
18305    );
18306    assert_hunk_revert(
18307        indoc! {r#"struct Row;
18308                   struct Row1;
18309                   struct Row33;
18310                   «ˇ
18311                   struct Row4;
18312                   struct» Row5;
18313                   «struct Row6;
18314                   ˇ»
18315                   struct Row99;
18316                   struct Row9;
18317                   struct Row10;"#},
18318        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18319        indoc! {r#"struct Row;
18320                   struct Row1;
18321                   struct Row33;
18322                   «ˇ
18323                   struct Row4;
18324                   struct» Row5;
18325                   «struct Row6;
18326                   ˇ»
18327                   struct Row99;
18328                   struct Row9;
18329                   struct Row10;"#},
18330        base_text,
18331        &mut cx,
18332    );
18333
18334    assert_hunk_revert(
18335        indoc! {r#"ˇstruct Row1.1;
18336                   struct Row1;
18337                   «ˇstr»uct Row22;
18338
18339                   struct ˇRow44;
18340                   struct Row5;
18341                   struct «Rˇ»ow66;ˇ
18342
18343                   «struˇ»ct Row88;
18344                   struct Row9;
18345                   struct Row1011;ˇ"#},
18346        vec![
18347            DiffHunkStatusKind::Modified,
18348            DiffHunkStatusKind::Modified,
18349            DiffHunkStatusKind::Modified,
18350            DiffHunkStatusKind::Modified,
18351            DiffHunkStatusKind::Modified,
18352            DiffHunkStatusKind::Modified,
18353        ],
18354        indoc! {r#"struct Row;
18355                   ˇstruct Row1;
18356                   struct Row2;
18357                   ˇ
18358                   struct Row4;
18359                   ˇstruct Row5;
18360                   struct Row6;
18361                   ˇ
18362                   struct Row8;
18363                   ˇstruct Row9;
18364                   struct Row10;ˇ"#},
18365        base_text,
18366        &mut cx,
18367    );
18368}
18369
18370#[gpui::test]
18371async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18372    init_test(cx, |_| {});
18373    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18374    let base_text = indoc! {r#"
18375        one
18376
18377        two
18378        three
18379        "#};
18380
18381    cx.set_head_text(base_text);
18382    cx.set_state("\nˇ\n");
18383    cx.executor().run_until_parked();
18384    cx.update_editor(|editor, _window, cx| {
18385        editor.expand_selected_diff_hunks(cx);
18386    });
18387    cx.executor().run_until_parked();
18388    cx.update_editor(|editor, window, cx| {
18389        editor.backspace(&Default::default(), window, cx);
18390    });
18391    cx.run_until_parked();
18392    cx.assert_state_with_diff(
18393        indoc! {r#"
18394
18395        - two
18396        - threeˇ
18397        +
18398        "#}
18399        .to_string(),
18400    );
18401}
18402
18403#[gpui::test]
18404async fn test_deletion_reverts(cx: &mut TestAppContext) {
18405    init_test(cx, |_| {});
18406    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18407    let base_text = indoc! {r#"struct Row;
18408struct Row1;
18409struct Row2;
18410
18411struct Row4;
18412struct Row5;
18413struct Row6;
18414
18415struct Row8;
18416struct Row9;
18417struct Row10;"#};
18418
18419    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18420    assert_hunk_revert(
18421        indoc! {r#"struct Row;
18422                   struct Row2;
18423
18424                   ˇstruct Row4;
18425                   struct Row5;
18426                   struct Row6;
18427                   ˇ
18428                   struct Row8;
18429                   struct Row10;"#},
18430        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18431        indoc! {r#"struct Row;
18432                   struct Row2;
18433
18434                   ˇstruct Row4;
18435                   struct Row5;
18436                   struct Row6;
18437                   ˇ
18438                   struct Row8;
18439                   struct Row10;"#},
18440        base_text,
18441        &mut cx,
18442    );
18443    assert_hunk_revert(
18444        indoc! {r#"struct Row;
18445                   struct Row2;
18446
18447                   «ˇstruct Row4;
18448                   struct» Row5;
18449                   «struct Row6;
18450                   ˇ»
18451                   struct Row8;
18452                   struct Row10;"#},
18453        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18454        indoc! {r#"struct Row;
18455                   struct Row2;
18456
18457                   «ˇstruct Row4;
18458                   struct» Row5;
18459                   «struct Row6;
18460                   ˇ»
18461                   struct Row8;
18462                   struct Row10;"#},
18463        base_text,
18464        &mut cx,
18465    );
18466
18467    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18468    assert_hunk_revert(
18469        indoc! {r#"struct Row;
18470                   ˇstruct Row2;
18471
18472                   struct Row4;
18473                   struct Row5;
18474                   struct Row6;
18475
18476                   struct Row8;ˇ
18477                   struct Row10;"#},
18478        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18479        indoc! {r#"struct Row;
18480                   struct Row1;
18481                   ˇstruct Row2;
18482
18483                   struct Row4;
18484                   struct Row5;
18485                   struct Row6;
18486
18487                   struct Row8;ˇ
18488                   struct Row9;
18489                   struct Row10;"#},
18490        base_text,
18491        &mut cx,
18492    );
18493    assert_hunk_revert(
18494        indoc! {r#"struct Row;
18495                   struct Row2«ˇ;
18496                   struct Row4;
18497                   struct» Row5;
18498                   «struct Row6;
18499
18500                   struct Row8;ˇ»
18501                   struct Row10;"#},
18502        vec![
18503            DiffHunkStatusKind::Deleted,
18504            DiffHunkStatusKind::Deleted,
18505            DiffHunkStatusKind::Deleted,
18506        ],
18507        indoc! {r#"struct Row;
18508                   struct Row1;
18509                   struct Row2«ˇ;
18510
18511                   struct Row4;
18512                   struct» Row5;
18513                   «struct Row6;
18514
18515                   struct Row8;ˇ»
18516                   struct Row9;
18517                   struct Row10;"#},
18518        base_text,
18519        &mut cx,
18520    );
18521}
18522
18523#[gpui::test]
18524async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18525    init_test(cx, |_| {});
18526
18527    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18528    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18529    let base_text_3 =
18530        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18531
18532    let text_1 = edit_first_char_of_every_line(base_text_1);
18533    let text_2 = edit_first_char_of_every_line(base_text_2);
18534    let text_3 = edit_first_char_of_every_line(base_text_3);
18535
18536    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18537    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18538    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18539
18540    let multibuffer = cx.new(|cx| {
18541        let mut multibuffer = MultiBuffer::new(ReadWrite);
18542        multibuffer.push_excerpts(
18543            buffer_1.clone(),
18544            [
18545                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18546                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18547                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18548            ],
18549            cx,
18550        );
18551        multibuffer.push_excerpts(
18552            buffer_2.clone(),
18553            [
18554                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18555                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18556                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18557            ],
18558            cx,
18559        );
18560        multibuffer.push_excerpts(
18561            buffer_3.clone(),
18562            [
18563                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18564                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18565                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18566            ],
18567            cx,
18568        );
18569        multibuffer
18570    });
18571
18572    let fs = FakeFs::new(cx.executor());
18573    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
18574    let (editor, cx) = cx
18575        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
18576    editor.update_in(cx, |editor, _window, cx| {
18577        for (buffer, diff_base) in [
18578            (buffer_1.clone(), base_text_1),
18579            (buffer_2.clone(), base_text_2),
18580            (buffer_3.clone(), base_text_3),
18581        ] {
18582            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18583            editor
18584                .buffer
18585                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18586        }
18587    });
18588    cx.executor().run_until_parked();
18589
18590    editor.update_in(cx, |editor, window, cx| {
18591        assert_eq!(editor.text(cx), "Xaaa\nXbbb\nXccc\n\nXfff\nXggg\n\nXjjj\nXlll\nXmmm\nXnnn\n\nXqqq\nXrrr\n\nXuuu\nXvvv\nXwww\nXxxx\n\nX{{{\nX|||\n\nX\u{7f}\u{7f}\u{7f}");
18592        editor.select_all(&SelectAll, window, cx);
18593        editor.git_restore(&Default::default(), window, cx);
18594    });
18595    cx.executor().run_until_parked();
18596
18597    // When all ranges are selected, all buffer hunks are reverted.
18598    editor.update(cx, |editor, cx| {
18599        assert_eq!(editor.text(cx), "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n\n\nllll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu\n\n\nvvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}\n\n");
18600    });
18601    buffer_1.update(cx, |buffer, _| {
18602        assert_eq!(buffer.text(), base_text_1);
18603    });
18604    buffer_2.update(cx, |buffer, _| {
18605        assert_eq!(buffer.text(), base_text_2);
18606    });
18607    buffer_3.update(cx, |buffer, _| {
18608        assert_eq!(buffer.text(), base_text_3);
18609    });
18610
18611    editor.update_in(cx, |editor, window, cx| {
18612        editor.undo(&Default::default(), window, cx);
18613    });
18614
18615    editor.update_in(cx, |editor, window, cx| {
18616        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18617            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
18618        });
18619        editor.git_restore(&Default::default(), window, cx);
18620    });
18621
18622    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
18623    // but not affect buffer_2 and its related excerpts.
18624    editor.update(cx, |editor, cx| {
18625        assert_eq!(
18626            editor.text(cx),
18627            "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n\n\nXlll\nXmmm\nXnnn\n\nXqqq\nXrrr\n\nXuuu\nXvvv\nXwww\nXxxx\n\nX{{{\nX|||\n\nX\u{7f}\u{7f}\u{7f}"
18628        );
18629    });
18630    buffer_1.update(cx, |buffer, _| {
18631        assert_eq!(buffer.text(), base_text_1);
18632    });
18633    buffer_2.update(cx, |buffer, _| {
18634        assert_eq!(
18635            buffer.text(),
18636            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
18637        );
18638    });
18639    buffer_3.update(cx, |buffer, _| {
18640        assert_eq!(
18641            buffer.text(),
18642            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
18643        );
18644    });
18645
18646    fn edit_first_char_of_every_line(text: &str) -> String {
18647        text.split('\n')
18648            .map(|line| format!("X{}", &line[1..]))
18649            .collect::<Vec<_>>()
18650            .join("\n")
18651    }
18652}
18653
18654#[gpui::test]
18655async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
18656    init_test(cx, |_| {});
18657
18658    let cols = 4;
18659    let rows = 10;
18660    let sample_text_1 = sample_text(rows, cols, 'a');
18661    assert_eq!(
18662        sample_text_1,
18663        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
18664    );
18665    let sample_text_2 = sample_text(rows, cols, 'l');
18666    assert_eq!(
18667        sample_text_2,
18668        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
18669    );
18670    let sample_text_3 = sample_text(rows, cols, 'v');
18671    assert_eq!(
18672        sample_text_3,
18673        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
18674    );
18675
18676    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
18677    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
18678    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
18679
18680    let multi_buffer = cx.new(|cx| {
18681        let mut multibuffer = MultiBuffer::new(ReadWrite);
18682        multibuffer.push_excerpts(
18683            buffer_1.clone(),
18684            [
18685                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18686                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18687                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18688            ],
18689            cx,
18690        );
18691        multibuffer.push_excerpts(
18692            buffer_2.clone(),
18693            [
18694                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18695                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18696                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18697            ],
18698            cx,
18699        );
18700        multibuffer.push_excerpts(
18701            buffer_3.clone(),
18702            [
18703                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18704                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18705                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18706            ],
18707            cx,
18708        );
18709        multibuffer
18710    });
18711
18712    let fs = FakeFs::new(cx.executor());
18713    fs.insert_tree(
18714        "/a",
18715        json!({
18716            "main.rs": sample_text_1,
18717            "other.rs": sample_text_2,
18718            "lib.rs": sample_text_3,
18719        }),
18720    )
18721    .await;
18722    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18723    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18724    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18725    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18726        Editor::new(
18727            EditorMode::full(),
18728            multi_buffer,
18729            Some(project.clone()),
18730            window,
18731            cx,
18732        )
18733    });
18734    let multibuffer_item_id = workspace
18735        .update(cx, |workspace, window, cx| {
18736            assert!(
18737                workspace.active_item(cx).is_none(),
18738                "active item should be None before the first item is added"
18739            );
18740            workspace.add_item_to_active_pane(
18741                Box::new(multi_buffer_editor.clone()),
18742                None,
18743                true,
18744                window,
18745                cx,
18746            );
18747            let active_item = workspace
18748                .active_item(cx)
18749                .expect("should have an active item after adding the multi buffer");
18750            assert!(
18751                !active_item.is_singleton(cx),
18752                "A multi buffer was expected to active after adding"
18753            );
18754            active_item.item_id()
18755        })
18756        .unwrap();
18757    cx.executor().run_until_parked();
18758
18759    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18760        editor.change_selections(
18761            SelectionEffects::scroll(Autoscroll::Next),
18762            window,
18763            cx,
18764            |s| s.select_ranges(Some(1..2)),
18765        );
18766        editor.open_excerpts(&OpenExcerpts, window, cx);
18767    });
18768    cx.executor().run_until_parked();
18769    let first_item_id = workspace
18770        .update(cx, |workspace, window, cx| {
18771            let active_item = workspace
18772                .active_item(cx)
18773                .expect("should have an active item after navigating into the 1st buffer");
18774            let first_item_id = active_item.item_id();
18775            assert_ne!(
18776                first_item_id, multibuffer_item_id,
18777                "Should navigate into the 1st buffer and activate it"
18778            );
18779            assert!(
18780                active_item.is_singleton(cx),
18781                "New active item should be a singleton buffer"
18782            );
18783            assert_eq!(
18784                active_item
18785                    .act_as::<Editor>(cx)
18786                    .expect("should have navigated into an editor for the 1st buffer")
18787                    .read(cx)
18788                    .text(cx),
18789                sample_text_1
18790            );
18791
18792            workspace
18793                .go_back(workspace.active_pane().downgrade(), window, cx)
18794                .detach_and_log_err(cx);
18795
18796            first_item_id
18797        })
18798        .unwrap();
18799    cx.executor().run_until_parked();
18800    workspace
18801        .update(cx, |workspace, _, cx| {
18802            let active_item = workspace
18803                .active_item(cx)
18804                .expect("should have an active item after navigating back");
18805            assert_eq!(
18806                active_item.item_id(),
18807                multibuffer_item_id,
18808                "Should navigate back to the multi buffer"
18809            );
18810            assert!(!active_item.is_singleton(cx));
18811        })
18812        .unwrap();
18813
18814    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18815        editor.change_selections(
18816            SelectionEffects::scroll(Autoscroll::Next),
18817            window,
18818            cx,
18819            |s| s.select_ranges(Some(39..40)),
18820        );
18821        editor.open_excerpts(&OpenExcerpts, window, cx);
18822    });
18823    cx.executor().run_until_parked();
18824    let second_item_id = workspace
18825        .update(cx, |workspace, window, cx| {
18826            let active_item = workspace
18827                .active_item(cx)
18828                .expect("should have an active item after navigating into the 2nd buffer");
18829            let second_item_id = active_item.item_id();
18830            assert_ne!(
18831                second_item_id, multibuffer_item_id,
18832                "Should navigate away from the multibuffer"
18833            );
18834            assert_ne!(
18835                second_item_id, first_item_id,
18836                "Should navigate into the 2nd buffer and activate it"
18837            );
18838            assert!(
18839                active_item.is_singleton(cx),
18840                "New active item should be a singleton buffer"
18841            );
18842            assert_eq!(
18843                active_item
18844                    .act_as::<Editor>(cx)
18845                    .expect("should have navigated into an editor")
18846                    .read(cx)
18847                    .text(cx),
18848                sample_text_2
18849            );
18850
18851            workspace
18852                .go_back(workspace.active_pane().downgrade(), window, cx)
18853                .detach_and_log_err(cx);
18854
18855            second_item_id
18856        })
18857        .unwrap();
18858    cx.executor().run_until_parked();
18859    workspace
18860        .update(cx, |workspace, _, cx| {
18861            let active_item = workspace
18862                .active_item(cx)
18863                .expect("should have an active item after navigating back from the 2nd buffer");
18864            assert_eq!(
18865                active_item.item_id(),
18866                multibuffer_item_id,
18867                "Should navigate back from the 2nd buffer to the multi buffer"
18868            );
18869            assert!(!active_item.is_singleton(cx));
18870        })
18871        .unwrap();
18872
18873    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18874        editor.change_selections(
18875            SelectionEffects::scroll(Autoscroll::Next),
18876            window,
18877            cx,
18878            |s| s.select_ranges(Some(70..70)),
18879        );
18880        editor.open_excerpts(&OpenExcerpts, window, cx);
18881    });
18882    cx.executor().run_until_parked();
18883    workspace
18884        .update(cx, |workspace, window, cx| {
18885            let active_item = workspace
18886                .active_item(cx)
18887                .expect("should have an active item after navigating into the 3rd buffer");
18888            let third_item_id = active_item.item_id();
18889            assert_ne!(
18890                third_item_id, multibuffer_item_id,
18891                "Should navigate into the 3rd buffer and activate it"
18892            );
18893            assert_ne!(third_item_id, first_item_id);
18894            assert_ne!(third_item_id, second_item_id);
18895            assert!(
18896                active_item.is_singleton(cx),
18897                "New active item should be a singleton buffer"
18898            );
18899            assert_eq!(
18900                active_item
18901                    .act_as::<Editor>(cx)
18902                    .expect("should have navigated into an editor")
18903                    .read(cx)
18904                    .text(cx),
18905                sample_text_3
18906            );
18907
18908            workspace
18909                .go_back(workspace.active_pane().downgrade(), window, cx)
18910                .detach_and_log_err(cx);
18911        })
18912        .unwrap();
18913    cx.executor().run_until_parked();
18914    workspace
18915        .update(cx, |workspace, _, cx| {
18916            let active_item = workspace
18917                .active_item(cx)
18918                .expect("should have an active item after navigating back from the 3rd buffer");
18919            assert_eq!(
18920                active_item.item_id(),
18921                multibuffer_item_id,
18922                "Should navigate back from the 3rd buffer to the multi buffer"
18923            );
18924            assert!(!active_item.is_singleton(cx));
18925        })
18926        .unwrap();
18927}
18928
18929#[gpui::test]
18930async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18931    init_test(cx, |_| {});
18932
18933    let mut cx = EditorTestContext::new(cx).await;
18934
18935    let diff_base = r#"
18936        use some::mod;
18937
18938        const A: u32 = 42;
18939
18940        fn main() {
18941            println!("hello");
18942
18943            println!("world");
18944        }
18945        "#
18946    .unindent();
18947
18948    cx.set_state(
18949        &r#"
18950        use some::modified;
18951
18952        ˇ
18953        fn main() {
18954            println!("hello there");
18955
18956            println!("around the");
18957            println!("world");
18958        }
18959        "#
18960        .unindent(),
18961    );
18962
18963    cx.set_head_text(&diff_base);
18964    executor.run_until_parked();
18965
18966    cx.update_editor(|editor, window, cx| {
18967        editor.go_to_next_hunk(&GoToHunk, window, cx);
18968        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18969    });
18970    executor.run_until_parked();
18971    cx.assert_state_with_diff(
18972        r#"
18973          use some::modified;
18974
18975
18976          fn main() {
18977        -     println!("hello");
18978        + ˇ    println!("hello there");
18979
18980              println!("around the");
18981              println!("world");
18982          }
18983        "#
18984        .unindent(),
18985    );
18986
18987    cx.update_editor(|editor, window, cx| {
18988        for _ in 0..2 {
18989            editor.go_to_next_hunk(&GoToHunk, window, cx);
18990            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18991        }
18992    });
18993    executor.run_until_parked();
18994    cx.assert_state_with_diff(
18995        r#"
18996        - use some::mod;
18997        + ˇuse some::modified;
18998
18999
19000          fn main() {
19001        -     println!("hello");
19002        +     println!("hello there");
19003
19004        +     println!("around the");
19005              println!("world");
19006          }
19007        "#
19008        .unindent(),
19009    );
19010
19011    cx.update_editor(|editor, window, cx| {
19012        editor.go_to_next_hunk(&GoToHunk, window, cx);
19013        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19014    });
19015    executor.run_until_parked();
19016    cx.assert_state_with_diff(
19017        r#"
19018        - use some::mod;
19019        + use some::modified;
19020
19021        - const A: u32 = 42;
19022          ˇ
19023          fn main() {
19024        -     println!("hello");
19025        +     println!("hello there");
19026
19027        +     println!("around the");
19028              println!("world");
19029          }
19030        "#
19031        .unindent(),
19032    );
19033
19034    cx.update_editor(|editor, window, cx| {
19035        editor.cancel(&Cancel, window, cx);
19036    });
19037
19038    cx.assert_state_with_diff(
19039        r#"
19040          use some::modified;
19041
19042          ˇ
19043          fn main() {
19044              println!("hello there");
19045
19046              println!("around the");
19047              println!("world");
19048          }
19049        "#
19050        .unindent(),
19051    );
19052}
19053
19054#[gpui::test]
19055async fn test_diff_base_change_with_expanded_diff_hunks(
19056    executor: BackgroundExecutor,
19057    cx: &mut TestAppContext,
19058) {
19059    init_test(cx, |_| {});
19060
19061    let mut cx = EditorTestContext::new(cx).await;
19062
19063    let diff_base = r#"
19064        use some::mod1;
19065        use some::mod2;
19066
19067        const A: u32 = 42;
19068        const B: u32 = 42;
19069        const C: u32 = 42;
19070
19071        fn main() {
19072            println!("hello");
19073
19074            println!("world");
19075        }
19076        "#
19077    .unindent();
19078
19079    cx.set_state(
19080        &r#"
19081        use some::mod2;
19082
19083        const A: u32 = 42;
19084        const C: u32 = 42;
19085
19086        fn main(ˇ) {
19087            //println!("hello");
19088
19089            println!("world");
19090            //
19091            //
19092        }
19093        "#
19094        .unindent(),
19095    );
19096
19097    cx.set_head_text(&diff_base);
19098    executor.run_until_parked();
19099
19100    cx.update_editor(|editor, window, cx| {
19101        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19102    });
19103    executor.run_until_parked();
19104    cx.assert_state_with_diff(
19105        r#"
19106        - use some::mod1;
19107          use some::mod2;
19108
19109          const A: u32 = 42;
19110        - const B: u32 = 42;
19111          const C: u32 = 42;
19112
19113          fn main(ˇ) {
19114        -     println!("hello");
19115        +     //println!("hello");
19116
19117              println!("world");
19118        +     //
19119        +     //
19120          }
19121        "#
19122        .unindent(),
19123    );
19124
19125    cx.set_head_text("new diff base!");
19126    executor.run_until_parked();
19127    cx.assert_state_with_diff(
19128        r#"
19129        - new diff base!
19130        + use some::mod2;
19131        +
19132        + const A: u32 = 42;
19133        + const C: u32 = 42;
19134        +
19135        + fn main(ˇ) {
19136        +     //println!("hello");
19137        +
19138        +     println!("world");
19139        +     //
19140        +     //
19141        + }
19142        "#
19143        .unindent(),
19144    );
19145}
19146
19147#[gpui::test]
19148async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19149    init_test(cx, |_| {});
19150
19151    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19152    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19153    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19154    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19155    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19156    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19157
19158    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19159    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19160    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19161
19162    let multi_buffer = cx.new(|cx| {
19163        let mut multibuffer = MultiBuffer::new(ReadWrite);
19164        multibuffer.push_excerpts(
19165            buffer_1.clone(),
19166            [
19167                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19168                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19169                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19170            ],
19171            cx,
19172        );
19173        multibuffer.push_excerpts(
19174            buffer_2.clone(),
19175            [
19176                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19177                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19178                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19179            ],
19180            cx,
19181        );
19182        multibuffer.push_excerpts(
19183            buffer_3.clone(),
19184            [
19185                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19186                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19187                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19188            ],
19189            cx,
19190        );
19191        multibuffer
19192    });
19193
19194    let editor =
19195        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19196    editor
19197        .update(cx, |editor, _window, cx| {
19198            for (buffer, diff_base) in [
19199                (buffer_1.clone(), file_1_old),
19200                (buffer_2.clone(), file_2_old),
19201                (buffer_3.clone(), file_3_old),
19202            ] {
19203                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19204                editor
19205                    .buffer
19206                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19207            }
19208        })
19209        .unwrap();
19210
19211    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19212    cx.run_until_parked();
19213
19214    cx.assert_editor_state(
19215        &"
19216            ˇaaa
19217            ccc
19218            ddd
19219
19220            ggg
19221            hhh
19222
19223
19224            lll
19225            mmm
19226            NNN
19227
19228            qqq
19229            rrr
19230
19231            uuu
19232            111
19233            222
19234            333
19235
19236            666
19237            777
19238
19239            000
19240            !!!"
19241        .unindent(),
19242    );
19243
19244    cx.update_editor(|editor, window, cx| {
19245        editor.select_all(&SelectAll, window, cx);
19246        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19247    });
19248    cx.executor().run_until_parked();
19249
19250    cx.assert_state_with_diff(
19251        "
19252            «aaa
19253          - bbb
19254            ccc
19255            ddd
19256
19257            ggg
19258            hhh
19259
19260
19261            lll
19262            mmm
19263          - nnn
19264          + NNN
19265
19266            qqq
19267            rrr
19268
19269            uuu
19270            111
19271            222
19272            333
19273
19274          + 666
19275            777
19276
19277            000
19278            !!!ˇ»"
19279            .unindent(),
19280    );
19281}
19282
19283#[gpui::test]
19284async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19285    init_test(cx, |_| {});
19286
19287    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19288    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19289
19290    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19291    let multi_buffer = cx.new(|cx| {
19292        let mut multibuffer = MultiBuffer::new(ReadWrite);
19293        multibuffer.push_excerpts(
19294            buffer.clone(),
19295            [
19296                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19297                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19298                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19299            ],
19300            cx,
19301        );
19302        multibuffer
19303    });
19304
19305    let editor =
19306        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19307    editor
19308        .update(cx, |editor, _window, cx| {
19309            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19310            editor
19311                .buffer
19312                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19313        })
19314        .unwrap();
19315
19316    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19317    cx.run_until_parked();
19318
19319    cx.update_editor(|editor, window, cx| {
19320        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19321    });
19322    cx.executor().run_until_parked();
19323
19324    // When the start of a hunk coincides with the start of its excerpt,
19325    // the hunk is expanded. When the start of a hunk is earlier than
19326    // the start of its excerpt, the hunk is not expanded.
19327    cx.assert_state_with_diff(
19328        "
19329            ˇaaa
19330          - bbb
19331          + BBB
19332
19333          - ddd
19334          - eee
19335          + DDD
19336          + EEE
19337            fff
19338
19339            iii
19340        "
19341        .unindent(),
19342    );
19343}
19344
19345#[gpui::test]
19346async fn test_edits_around_expanded_insertion_hunks(
19347    executor: BackgroundExecutor,
19348    cx: &mut TestAppContext,
19349) {
19350    init_test(cx, |_| {});
19351
19352    let mut cx = EditorTestContext::new(cx).await;
19353
19354    let diff_base = r#"
19355        use some::mod1;
19356        use some::mod2;
19357
19358        const A: u32 = 42;
19359
19360        fn main() {
19361            println!("hello");
19362
19363            println!("world");
19364        }
19365        "#
19366    .unindent();
19367    executor.run_until_parked();
19368    cx.set_state(
19369        &r#"
19370        use some::mod1;
19371        use some::mod2;
19372
19373        const A: u32 = 42;
19374        const B: u32 = 42;
19375        const C: u32 = 42;
19376        ˇ
19377
19378        fn main() {
19379            println!("hello");
19380
19381            println!("world");
19382        }
19383        "#
19384        .unindent(),
19385    );
19386
19387    cx.set_head_text(&diff_base);
19388    executor.run_until_parked();
19389
19390    cx.update_editor(|editor, window, cx| {
19391        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19392    });
19393    executor.run_until_parked();
19394
19395    cx.assert_state_with_diff(
19396        r#"
19397        use some::mod1;
19398        use some::mod2;
19399
19400        const A: u32 = 42;
19401      + const B: u32 = 42;
19402      + const C: u32 = 42;
19403      + ˇ
19404
19405        fn main() {
19406            println!("hello");
19407
19408            println!("world");
19409        }
19410      "#
19411        .unindent(),
19412    );
19413
19414    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19415    executor.run_until_parked();
19416
19417    cx.assert_state_with_diff(
19418        r#"
19419        use some::mod1;
19420        use some::mod2;
19421
19422        const A: u32 = 42;
19423      + const B: u32 = 42;
19424      + const C: u32 = 42;
19425      + const D: u32 = 42;
19426      + ˇ
19427
19428        fn main() {
19429            println!("hello");
19430
19431            println!("world");
19432        }
19433      "#
19434        .unindent(),
19435    );
19436
19437    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19438    executor.run_until_parked();
19439
19440    cx.assert_state_with_diff(
19441        r#"
19442        use some::mod1;
19443        use some::mod2;
19444
19445        const A: u32 = 42;
19446      + const B: u32 = 42;
19447      + const C: u32 = 42;
19448      + const D: u32 = 42;
19449      + const E: u32 = 42;
19450      + ˇ
19451
19452        fn main() {
19453            println!("hello");
19454
19455            println!("world");
19456        }
19457      "#
19458        .unindent(),
19459    );
19460
19461    cx.update_editor(|editor, window, cx| {
19462        editor.delete_line(&DeleteLine, window, cx);
19463    });
19464    executor.run_until_parked();
19465
19466    cx.assert_state_with_diff(
19467        r#"
19468        use some::mod1;
19469        use some::mod2;
19470
19471        const A: u32 = 42;
19472      + const B: u32 = 42;
19473      + const C: u32 = 42;
19474      + const D: u32 = 42;
19475      + const E: u32 = 42;
19476        ˇ
19477        fn main() {
19478            println!("hello");
19479
19480            println!("world");
19481        }
19482      "#
19483        .unindent(),
19484    );
19485
19486    cx.update_editor(|editor, window, cx| {
19487        editor.move_up(&MoveUp, window, cx);
19488        editor.delete_line(&DeleteLine, window, cx);
19489        editor.move_up(&MoveUp, window, cx);
19490        editor.delete_line(&DeleteLine, window, cx);
19491        editor.move_up(&MoveUp, window, cx);
19492        editor.delete_line(&DeleteLine, window, cx);
19493    });
19494    executor.run_until_parked();
19495    cx.assert_state_with_diff(
19496        r#"
19497        use some::mod1;
19498        use some::mod2;
19499
19500        const A: u32 = 42;
19501      + const B: u32 = 42;
19502        ˇ
19503        fn main() {
19504            println!("hello");
19505
19506            println!("world");
19507        }
19508      "#
19509        .unindent(),
19510    );
19511
19512    cx.update_editor(|editor, window, cx| {
19513        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19514        editor.delete_line(&DeleteLine, window, cx);
19515    });
19516    executor.run_until_parked();
19517    cx.assert_state_with_diff(
19518        r#"
19519        ˇ
19520        fn main() {
19521            println!("hello");
19522
19523            println!("world");
19524        }
19525      "#
19526        .unindent(),
19527    );
19528}
19529
19530#[gpui::test]
19531async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19532    init_test(cx, |_| {});
19533
19534    let mut cx = EditorTestContext::new(cx).await;
19535    cx.set_head_text(indoc! { "
19536        one
19537        two
19538        three
19539        four
19540        five
19541        "
19542    });
19543    cx.set_state(indoc! { "
19544        one
19545        ˇthree
19546        five
19547    "});
19548    cx.run_until_parked();
19549    cx.update_editor(|editor, window, cx| {
19550        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19551    });
19552    cx.assert_state_with_diff(
19553        indoc! { "
19554        one
19555      - two
19556        ˇthree
19557      - four
19558        five
19559    "}
19560        .to_string(),
19561    );
19562    cx.update_editor(|editor, window, cx| {
19563        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19564    });
19565
19566    cx.assert_state_with_diff(
19567        indoc! { "
19568        one
19569        ˇthree
19570        five
19571    "}
19572        .to_string(),
19573    );
19574
19575    cx.set_state(indoc! { "
19576        one
19577        ˇTWO
19578        three
19579        four
19580        five
19581    "});
19582    cx.run_until_parked();
19583    cx.update_editor(|editor, window, cx| {
19584        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19585    });
19586
19587    cx.assert_state_with_diff(
19588        indoc! { "
19589            one
19590          - two
19591          + ˇTWO
19592            three
19593            four
19594            five
19595        "}
19596        .to_string(),
19597    );
19598    cx.update_editor(|editor, window, cx| {
19599        editor.move_up(&Default::default(), window, cx);
19600        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19601    });
19602    cx.assert_state_with_diff(
19603        indoc! { "
19604            one
19605            ˇTWO
19606            three
19607            four
19608            five
19609        "}
19610        .to_string(),
19611    );
19612}
19613
19614#[gpui::test]
19615async fn test_edits_around_expanded_deletion_hunks(
19616    executor: BackgroundExecutor,
19617    cx: &mut TestAppContext,
19618) {
19619    init_test(cx, |_| {});
19620
19621    let mut cx = EditorTestContext::new(cx).await;
19622
19623    let diff_base = r#"
19624        use some::mod1;
19625        use some::mod2;
19626
19627        const A: u32 = 42;
19628        const B: u32 = 42;
19629        const C: u32 = 42;
19630
19631
19632        fn main() {
19633            println!("hello");
19634
19635            println!("world");
19636        }
19637    "#
19638    .unindent();
19639    executor.run_until_parked();
19640    cx.set_state(
19641        &r#"
19642        use some::mod1;
19643        use some::mod2;
19644
19645        ˇconst B: u32 = 42;
19646        const C: u32 = 42;
19647
19648
19649        fn main() {
19650            println!("hello");
19651
19652            println!("world");
19653        }
19654        "#
19655        .unindent(),
19656    );
19657
19658    cx.set_head_text(&diff_base);
19659    executor.run_until_parked();
19660
19661    cx.update_editor(|editor, window, cx| {
19662        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19663    });
19664    executor.run_until_parked();
19665
19666    cx.assert_state_with_diff(
19667        r#"
19668        use some::mod1;
19669        use some::mod2;
19670
19671      - const A: u32 = 42;
19672        ˇconst B: u32 = 42;
19673        const C: u32 = 42;
19674
19675
19676        fn main() {
19677            println!("hello");
19678
19679            println!("world");
19680        }
19681      "#
19682        .unindent(),
19683    );
19684
19685    cx.update_editor(|editor, window, cx| {
19686        editor.delete_line(&DeleteLine, window, cx);
19687    });
19688    executor.run_until_parked();
19689    cx.assert_state_with_diff(
19690        r#"
19691        use some::mod1;
19692        use some::mod2;
19693
19694      - const A: u32 = 42;
19695      - const B: u32 = 42;
19696        ˇconst C: u32 = 42;
19697
19698
19699        fn main() {
19700            println!("hello");
19701
19702            println!("world");
19703        }
19704      "#
19705        .unindent(),
19706    );
19707
19708    cx.update_editor(|editor, window, cx| {
19709        editor.delete_line(&DeleteLine, window, cx);
19710    });
19711    executor.run_until_parked();
19712    cx.assert_state_with_diff(
19713        r#"
19714        use some::mod1;
19715        use some::mod2;
19716
19717      - const A: u32 = 42;
19718      - const B: u32 = 42;
19719      - const C: u32 = 42;
19720        ˇ
19721
19722        fn main() {
19723            println!("hello");
19724
19725            println!("world");
19726        }
19727      "#
19728        .unindent(),
19729    );
19730
19731    cx.update_editor(|editor, window, cx| {
19732        editor.handle_input("replacement", window, cx);
19733    });
19734    executor.run_until_parked();
19735    cx.assert_state_with_diff(
19736        r#"
19737        use some::mod1;
19738        use some::mod2;
19739
19740      - const A: u32 = 42;
19741      - const B: u32 = 42;
19742      - const C: u32 = 42;
19743      -
19744      + replacementˇ
19745
19746        fn main() {
19747            println!("hello");
19748
19749            println!("world");
19750        }
19751      "#
19752        .unindent(),
19753    );
19754}
19755
19756#[gpui::test]
19757async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19758    init_test(cx, |_| {});
19759
19760    let mut cx = EditorTestContext::new(cx).await;
19761
19762    let base_text = r#"
19763        one
19764        two
19765        three
19766        four
19767        five
19768    "#
19769    .unindent();
19770    executor.run_until_parked();
19771    cx.set_state(
19772        &r#"
19773        one
19774        two
19775        fˇour
19776        five
19777        "#
19778        .unindent(),
19779    );
19780
19781    cx.set_head_text(&base_text);
19782    executor.run_until_parked();
19783
19784    cx.update_editor(|editor, window, cx| {
19785        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19786    });
19787    executor.run_until_parked();
19788
19789    cx.assert_state_with_diff(
19790        r#"
19791          one
19792          two
19793        - three
19794          fˇour
19795          five
19796        "#
19797        .unindent(),
19798    );
19799
19800    cx.update_editor(|editor, window, cx| {
19801        editor.backspace(&Backspace, window, cx);
19802        editor.backspace(&Backspace, window, cx);
19803    });
19804    executor.run_until_parked();
19805    cx.assert_state_with_diff(
19806        r#"
19807          one
19808          two
19809        - threeˇ
19810        - four
19811        + our
19812          five
19813        "#
19814        .unindent(),
19815    );
19816}
19817
19818#[gpui::test]
19819async fn test_edit_after_expanded_modification_hunk(
19820    executor: BackgroundExecutor,
19821    cx: &mut TestAppContext,
19822) {
19823    init_test(cx, |_| {});
19824
19825    let mut cx = EditorTestContext::new(cx).await;
19826
19827    let diff_base = r#"
19828        use some::mod1;
19829        use some::mod2;
19830
19831        const A: u32 = 42;
19832        const B: u32 = 42;
19833        const C: u32 = 42;
19834        const D: u32 = 42;
19835
19836
19837        fn main() {
19838            println!("hello");
19839
19840            println!("world");
19841        }"#
19842    .unindent();
19843
19844    cx.set_state(
19845        &r#"
19846        use some::mod1;
19847        use some::mod2;
19848
19849        const A: u32 = 42;
19850        const B: u32 = 42;
19851        const C: u32 = 43ˇ
19852        const D: u32 = 42;
19853
19854
19855        fn main() {
19856            println!("hello");
19857
19858            println!("world");
19859        }"#
19860        .unindent(),
19861    );
19862
19863    cx.set_head_text(&diff_base);
19864    executor.run_until_parked();
19865    cx.update_editor(|editor, window, cx| {
19866        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19867    });
19868    executor.run_until_parked();
19869
19870    cx.assert_state_with_diff(
19871        r#"
19872        use some::mod1;
19873        use some::mod2;
19874
19875        const A: u32 = 42;
19876        const B: u32 = 42;
19877      - const C: u32 = 42;
19878      + const C: u32 = 43ˇ
19879        const D: u32 = 42;
19880
19881
19882        fn main() {
19883            println!("hello");
19884
19885            println!("world");
19886        }"#
19887        .unindent(),
19888    );
19889
19890    cx.update_editor(|editor, window, cx| {
19891        editor.handle_input("\nnew_line\n", window, cx);
19892    });
19893    executor.run_until_parked();
19894
19895    cx.assert_state_with_diff(
19896        r#"
19897        use some::mod1;
19898        use some::mod2;
19899
19900        const A: u32 = 42;
19901        const B: u32 = 42;
19902      - const C: u32 = 42;
19903      + const C: u32 = 43
19904      + new_line
19905      + ˇ
19906        const D: u32 = 42;
19907
19908
19909        fn main() {
19910            println!("hello");
19911
19912            println!("world");
19913        }"#
19914        .unindent(),
19915    );
19916}
19917
19918#[gpui::test]
19919async fn test_stage_and_unstage_added_file_hunk(
19920    executor: BackgroundExecutor,
19921    cx: &mut TestAppContext,
19922) {
19923    init_test(cx, |_| {});
19924
19925    let mut cx = EditorTestContext::new(cx).await;
19926    cx.update_editor(|editor, _, cx| {
19927        editor.set_expand_all_diff_hunks(cx);
19928    });
19929
19930    let working_copy = r#"
19931            ˇfn main() {
19932                println!("hello, world!");
19933            }
19934        "#
19935    .unindent();
19936
19937    cx.set_state(&working_copy);
19938    executor.run_until_parked();
19939
19940    cx.assert_state_with_diff(
19941        r#"
19942            + ˇfn main() {
19943            +     println!("hello, world!");
19944            + }
19945        "#
19946        .unindent(),
19947    );
19948    cx.assert_index_text(None);
19949
19950    cx.update_editor(|editor, window, cx| {
19951        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19952    });
19953    executor.run_until_parked();
19954    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
19955    cx.assert_state_with_diff(
19956        r#"
19957            + ˇfn main() {
19958            +     println!("hello, world!");
19959            + }
19960        "#
19961        .unindent(),
19962    );
19963
19964    cx.update_editor(|editor, window, cx| {
19965        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19966    });
19967    executor.run_until_parked();
19968    cx.assert_index_text(None);
19969}
19970
19971async fn setup_indent_guides_editor(
19972    text: &str,
19973    cx: &mut TestAppContext,
19974) -> (BufferId, EditorTestContext) {
19975    init_test(cx, |_| {});
19976
19977    let mut cx = EditorTestContext::new(cx).await;
19978
19979    let buffer_id = cx.update_editor(|editor, window, cx| {
19980        editor.set_text(text, window, cx);
19981        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
19982
19983        buffer_ids[0]
19984    });
19985
19986    (buffer_id, cx)
19987}
19988
19989fn assert_indent_guides(
19990    range: Range<u32>,
19991    expected: Vec<IndentGuide>,
19992    active_indices: Option<Vec<usize>>,
19993    cx: &mut EditorTestContext,
19994) {
19995    let indent_guides = cx.update_editor(|editor, window, cx| {
19996        let snapshot = editor.snapshot(window, cx).display_snapshot;
19997        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
19998            editor,
19999            MultiBufferRow(range.start)..MultiBufferRow(range.end),
20000            true,
20001            &snapshot,
20002            cx,
20003        );
20004
20005        indent_guides.sort_by(|a, b| {
20006            a.depth.cmp(&b.depth).then(
20007                a.start_row
20008                    .cmp(&b.start_row)
20009                    .then(a.end_row.cmp(&b.end_row)),
20010            )
20011        });
20012        indent_guides
20013    });
20014
20015    if let Some(expected) = active_indices {
20016        let active_indices = cx.update_editor(|editor, window, cx| {
20017            let snapshot = editor.snapshot(window, cx).display_snapshot;
20018            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
20019        });
20020
20021        assert_eq!(
20022            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
20023            expected,
20024            "Active indent guide indices do not match"
20025        );
20026    }
20027
20028    assert_eq!(indent_guides, expected, "Indent guides do not match");
20029}
20030
20031fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
20032    IndentGuide {
20033        buffer_id,
20034        start_row: MultiBufferRow(start_row),
20035        end_row: MultiBufferRow(end_row),
20036        depth,
20037        tab_size: 4,
20038        settings: IndentGuideSettings {
20039            enabled: true,
20040            line_width: 1,
20041            active_line_width: 1,
20042            coloring: IndentGuideColoring::default(),
20043            background_coloring: IndentGuideBackgroundColoring::default(),
20044        },
20045    }
20046}
20047
20048#[gpui::test]
20049async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
20050    let (buffer_id, mut cx) = setup_indent_guides_editor(
20051        &"
20052        fn main() {
20053            let a = 1;
20054        }"
20055        .unindent(),
20056        cx,
20057    )
20058    .await;
20059
20060    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20061}
20062
20063#[gpui::test]
20064async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
20065    let (buffer_id, mut cx) = setup_indent_guides_editor(
20066        &"
20067        fn main() {
20068            let a = 1;
20069            let b = 2;
20070        }"
20071        .unindent(),
20072        cx,
20073    )
20074    .await;
20075
20076    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20077}
20078
20079#[gpui::test]
20080async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20081    let (buffer_id, mut cx) = setup_indent_guides_editor(
20082        &"
20083        fn main() {
20084            let a = 1;
20085            if a == 3 {
20086                let b = 2;
20087            } else {
20088                let c = 3;
20089            }
20090        }"
20091        .unindent(),
20092        cx,
20093    )
20094    .await;
20095
20096    assert_indent_guides(
20097        0..8,
20098        vec![
20099            indent_guide(buffer_id, 1, 6, 0),
20100            indent_guide(buffer_id, 3, 3, 1),
20101            indent_guide(buffer_id, 5, 5, 1),
20102        ],
20103        None,
20104        &mut cx,
20105    );
20106}
20107
20108#[gpui::test]
20109async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20110    let (buffer_id, mut cx) = setup_indent_guides_editor(
20111        &"
20112        fn main() {
20113            let a = 1;
20114                let b = 2;
20115            let c = 3;
20116        }"
20117        .unindent(),
20118        cx,
20119    )
20120    .await;
20121
20122    assert_indent_guides(
20123        0..5,
20124        vec![
20125            indent_guide(buffer_id, 1, 3, 0),
20126            indent_guide(buffer_id, 2, 2, 1),
20127        ],
20128        None,
20129        &mut cx,
20130    );
20131}
20132
20133#[gpui::test]
20134async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20135    let (buffer_id, mut cx) = setup_indent_guides_editor(
20136        &"
20137        fn main() {
20138            let a = 1;
20139
20140            let c = 3;
20141        }"
20142        .unindent(),
20143        cx,
20144    )
20145    .await;
20146
20147    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20148}
20149
20150#[gpui::test]
20151async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20152    let (buffer_id, mut cx) = setup_indent_guides_editor(
20153        &"
20154        fn main() {
20155            let a = 1;
20156
20157            let c = 3;
20158
20159            if a == 3 {
20160                let b = 2;
20161            } else {
20162                let c = 3;
20163            }
20164        }"
20165        .unindent(),
20166        cx,
20167    )
20168    .await;
20169
20170    assert_indent_guides(
20171        0..11,
20172        vec![
20173            indent_guide(buffer_id, 1, 9, 0),
20174            indent_guide(buffer_id, 6, 6, 1),
20175            indent_guide(buffer_id, 8, 8, 1),
20176        ],
20177        None,
20178        &mut cx,
20179    );
20180}
20181
20182#[gpui::test]
20183async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20184    let (buffer_id, mut cx) = setup_indent_guides_editor(
20185        &"
20186        fn main() {
20187            let a = 1;
20188
20189            let c = 3;
20190
20191            if a == 3 {
20192                let b = 2;
20193            } else {
20194                let c = 3;
20195            }
20196        }"
20197        .unindent(),
20198        cx,
20199    )
20200    .await;
20201
20202    assert_indent_guides(
20203        1..11,
20204        vec![
20205            indent_guide(buffer_id, 1, 9, 0),
20206            indent_guide(buffer_id, 6, 6, 1),
20207            indent_guide(buffer_id, 8, 8, 1),
20208        ],
20209        None,
20210        &mut cx,
20211    );
20212}
20213
20214#[gpui::test]
20215async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20216    let (buffer_id, mut cx) = setup_indent_guides_editor(
20217        &"
20218        fn main() {
20219            let a = 1;
20220
20221            let c = 3;
20222
20223            if a == 3 {
20224                let b = 2;
20225            } else {
20226                let c = 3;
20227            }
20228        }"
20229        .unindent(),
20230        cx,
20231    )
20232    .await;
20233
20234    assert_indent_guides(
20235        1..10,
20236        vec![
20237            indent_guide(buffer_id, 1, 9, 0),
20238            indent_guide(buffer_id, 6, 6, 1),
20239            indent_guide(buffer_id, 8, 8, 1),
20240        ],
20241        None,
20242        &mut cx,
20243    );
20244}
20245
20246#[gpui::test]
20247async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20248    let (buffer_id, mut cx) = setup_indent_guides_editor(
20249        &"
20250        fn main() {
20251            if a {
20252                b(
20253                    c,
20254                    d,
20255                )
20256            } else {
20257                e(
20258                    f
20259                )
20260            }
20261        }"
20262        .unindent(),
20263        cx,
20264    )
20265    .await;
20266
20267    assert_indent_guides(
20268        0..11,
20269        vec![
20270            indent_guide(buffer_id, 1, 10, 0),
20271            indent_guide(buffer_id, 2, 5, 1),
20272            indent_guide(buffer_id, 7, 9, 1),
20273            indent_guide(buffer_id, 3, 4, 2),
20274            indent_guide(buffer_id, 8, 8, 2),
20275        ],
20276        None,
20277        &mut cx,
20278    );
20279
20280    cx.update_editor(|editor, window, cx| {
20281        editor.fold_at(MultiBufferRow(2), window, cx);
20282        assert_eq!(
20283            editor.display_text(cx),
20284            "
20285            fn main() {
20286                if a {
20287                    b(⋯
20288                    )
20289                } else {
20290                    e(
20291                        f
20292                    )
20293                }
20294            }"
20295            .unindent()
20296        );
20297    });
20298
20299    assert_indent_guides(
20300        0..11,
20301        vec![
20302            indent_guide(buffer_id, 1, 10, 0),
20303            indent_guide(buffer_id, 2, 5, 1),
20304            indent_guide(buffer_id, 7, 9, 1),
20305            indent_guide(buffer_id, 8, 8, 2),
20306        ],
20307        None,
20308        &mut cx,
20309    );
20310}
20311
20312#[gpui::test]
20313async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20314    let (buffer_id, mut cx) = setup_indent_guides_editor(
20315        &"
20316        block1
20317            block2
20318                block3
20319                    block4
20320            block2
20321        block1
20322        block1"
20323            .unindent(),
20324        cx,
20325    )
20326    .await;
20327
20328    assert_indent_guides(
20329        1..10,
20330        vec![
20331            indent_guide(buffer_id, 1, 4, 0),
20332            indent_guide(buffer_id, 2, 3, 1),
20333            indent_guide(buffer_id, 3, 3, 2),
20334        ],
20335        None,
20336        &mut cx,
20337    );
20338}
20339
20340#[gpui::test]
20341async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20342    let (buffer_id, mut cx) = setup_indent_guides_editor(
20343        &"
20344        block1
20345            block2
20346                block3
20347
20348        block1
20349        block1"
20350            .unindent(),
20351        cx,
20352    )
20353    .await;
20354
20355    assert_indent_guides(
20356        0..6,
20357        vec![
20358            indent_guide(buffer_id, 1, 2, 0),
20359            indent_guide(buffer_id, 2, 2, 1),
20360        ],
20361        None,
20362        &mut cx,
20363    );
20364}
20365
20366#[gpui::test]
20367async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20368    let (buffer_id, mut cx) = setup_indent_guides_editor(
20369        &"
20370        function component() {
20371        \treturn (
20372        \t\t\t
20373        \t\t<div>
20374        \t\t\t<abc></abc>
20375        \t\t</div>
20376        \t)
20377        }"
20378        .unindent(),
20379        cx,
20380    )
20381    .await;
20382
20383    assert_indent_guides(
20384        0..8,
20385        vec![
20386            indent_guide(buffer_id, 1, 6, 0),
20387            indent_guide(buffer_id, 2, 5, 1),
20388            indent_guide(buffer_id, 4, 4, 2),
20389        ],
20390        None,
20391        &mut cx,
20392    );
20393}
20394
20395#[gpui::test]
20396async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20397    let (buffer_id, mut cx) = setup_indent_guides_editor(
20398        &"
20399        function component() {
20400        \treturn (
20401        \t
20402        \t\t<div>
20403        \t\t\t<abc></abc>
20404        \t\t</div>
20405        \t)
20406        }"
20407        .unindent(),
20408        cx,
20409    )
20410    .await;
20411
20412    assert_indent_guides(
20413        0..8,
20414        vec![
20415            indent_guide(buffer_id, 1, 6, 0),
20416            indent_guide(buffer_id, 2, 5, 1),
20417            indent_guide(buffer_id, 4, 4, 2),
20418        ],
20419        None,
20420        &mut cx,
20421    );
20422}
20423
20424#[gpui::test]
20425async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20426    let (buffer_id, mut cx) = setup_indent_guides_editor(
20427        &"
20428        block1
20429
20430
20431
20432            block2
20433        "
20434        .unindent(),
20435        cx,
20436    )
20437    .await;
20438
20439    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20440}
20441
20442#[gpui::test]
20443async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20444    let (buffer_id, mut cx) = setup_indent_guides_editor(
20445        &"
20446        def a:
20447        \tb = 3
20448        \tif True:
20449        \t\tc = 4
20450        \t\td = 5
20451        \tprint(b)
20452        "
20453        .unindent(),
20454        cx,
20455    )
20456    .await;
20457
20458    assert_indent_guides(
20459        0..6,
20460        vec![
20461            indent_guide(buffer_id, 1, 5, 0),
20462            indent_guide(buffer_id, 3, 4, 1),
20463        ],
20464        None,
20465        &mut cx,
20466    );
20467}
20468
20469#[gpui::test]
20470async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20471    let (buffer_id, mut cx) = setup_indent_guides_editor(
20472        &"
20473    fn main() {
20474        let a = 1;
20475    }"
20476        .unindent(),
20477        cx,
20478    )
20479    .await;
20480
20481    cx.update_editor(|editor, window, cx| {
20482        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20483            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20484        });
20485    });
20486
20487    assert_indent_guides(
20488        0..3,
20489        vec![indent_guide(buffer_id, 1, 1, 0)],
20490        Some(vec![0]),
20491        &mut cx,
20492    );
20493}
20494
20495#[gpui::test]
20496async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20497    let (buffer_id, mut cx) = setup_indent_guides_editor(
20498        &"
20499    fn main() {
20500        if 1 == 2 {
20501            let a = 1;
20502        }
20503    }"
20504        .unindent(),
20505        cx,
20506    )
20507    .await;
20508
20509    cx.update_editor(|editor, window, cx| {
20510        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20511            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20512        });
20513    });
20514
20515    assert_indent_guides(
20516        0..4,
20517        vec![
20518            indent_guide(buffer_id, 1, 3, 0),
20519            indent_guide(buffer_id, 2, 2, 1),
20520        ],
20521        Some(vec![1]),
20522        &mut cx,
20523    );
20524
20525    cx.update_editor(|editor, window, cx| {
20526        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20527            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20528        });
20529    });
20530
20531    assert_indent_guides(
20532        0..4,
20533        vec![
20534            indent_guide(buffer_id, 1, 3, 0),
20535            indent_guide(buffer_id, 2, 2, 1),
20536        ],
20537        Some(vec![1]),
20538        &mut cx,
20539    );
20540
20541    cx.update_editor(|editor, window, cx| {
20542        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20543            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20544        });
20545    });
20546
20547    assert_indent_guides(
20548        0..4,
20549        vec![
20550            indent_guide(buffer_id, 1, 3, 0),
20551            indent_guide(buffer_id, 2, 2, 1),
20552        ],
20553        Some(vec![0]),
20554        &mut cx,
20555    );
20556}
20557
20558#[gpui::test]
20559async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
20560    let (buffer_id, mut cx) = setup_indent_guides_editor(
20561        &"
20562    fn main() {
20563        let a = 1;
20564
20565        let b = 2;
20566    }"
20567        .unindent(),
20568        cx,
20569    )
20570    .await;
20571
20572    cx.update_editor(|editor, window, cx| {
20573        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20574            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20575        });
20576    });
20577
20578    assert_indent_guides(
20579        0..5,
20580        vec![indent_guide(buffer_id, 1, 3, 0)],
20581        Some(vec![0]),
20582        &mut cx,
20583    );
20584}
20585
20586#[gpui::test]
20587async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
20588    let (buffer_id, mut cx) = setup_indent_guides_editor(
20589        &"
20590    def m:
20591        a = 1
20592        pass"
20593            .unindent(),
20594        cx,
20595    )
20596    .await;
20597
20598    cx.update_editor(|editor, window, cx| {
20599        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20600            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20601        });
20602    });
20603
20604    assert_indent_guides(
20605        0..3,
20606        vec![indent_guide(buffer_id, 1, 2, 0)],
20607        Some(vec![0]),
20608        &mut cx,
20609    );
20610}
20611
20612#[gpui::test]
20613async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
20614    init_test(cx, |_| {});
20615    let mut cx = EditorTestContext::new(cx).await;
20616    let text = indoc! {
20617        "
20618        impl A {
20619            fn b() {
20620                0;
20621                3;
20622                5;
20623                6;
20624                7;
20625            }
20626        }
20627        "
20628    };
20629    let base_text = indoc! {
20630        "
20631        impl A {
20632            fn b() {
20633                0;
20634                1;
20635                2;
20636                3;
20637                4;
20638            }
20639            fn c() {
20640                5;
20641                6;
20642                7;
20643            }
20644        }
20645        "
20646    };
20647
20648    cx.update_editor(|editor, window, cx| {
20649        editor.set_text(text, window, cx);
20650
20651        editor.buffer().update(cx, |multibuffer, cx| {
20652            let buffer = multibuffer.as_singleton().unwrap();
20653            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
20654
20655            multibuffer.set_all_diff_hunks_expanded(cx);
20656            multibuffer.add_diff(diff, cx);
20657
20658            buffer.read(cx).remote_id()
20659        })
20660    });
20661    cx.run_until_parked();
20662
20663    cx.assert_state_with_diff(
20664        indoc! { "
20665          impl A {
20666              fn b() {
20667                  0;
20668        -         1;
20669        -         2;
20670                  3;
20671        -         4;
20672        -     }
20673        -     fn c() {
20674                  5;
20675                  6;
20676                  7;
20677              }
20678          }
20679          ˇ"
20680        }
20681        .to_string(),
20682    );
20683
20684    let mut actual_guides = cx.update_editor(|editor, window, cx| {
20685        editor
20686            .snapshot(window, cx)
20687            .buffer_snapshot
20688            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
20689            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
20690            .collect::<Vec<_>>()
20691    });
20692    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
20693    assert_eq!(
20694        actual_guides,
20695        vec![
20696            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
20697            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
20698            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
20699        ]
20700    );
20701}
20702
20703#[gpui::test]
20704async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20705    init_test(cx, |_| {});
20706    let mut cx = EditorTestContext::new(cx).await;
20707
20708    let diff_base = r#"
20709        a
20710        b
20711        c
20712        "#
20713    .unindent();
20714
20715    cx.set_state(
20716        &r#"
20717        ˇA
20718        b
20719        C
20720        "#
20721        .unindent(),
20722    );
20723    cx.set_head_text(&diff_base);
20724    cx.update_editor(|editor, window, cx| {
20725        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20726    });
20727    executor.run_until_parked();
20728
20729    let both_hunks_expanded = r#"
20730        - a
20731        + ˇA
20732          b
20733        - c
20734        + C
20735        "#
20736    .unindent();
20737
20738    cx.assert_state_with_diff(both_hunks_expanded.clone());
20739
20740    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20741        let snapshot = editor.snapshot(window, cx);
20742        let hunks = editor
20743            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20744            .collect::<Vec<_>>();
20745        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20746        let buffer_id = hunks[0].buffer_id;
20747        hunks
20748            .into_iter()
20749            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20750            .collect::<Vec<_>>()
20751    });
20752    assert_eq!(hunk_ranges.len(), 2);
20753
20754    cx.update_editor(|editor, _, cx| {
20755        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20756    });
20757    executor.run_until_parked();
20758
20759    let second_hunk_expanded = r#"
20760          ˇA
20761          b
20762        - c
20763        + C
20764        "#
20765    .unindent();
20766
20767    cx.assert_state_with_diff(second_hunk_expanded);
20768
20769    cx.update_editor(|editor, _, cx| {
20770        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20771    });
20772    executor.run_until_parked();
20773
20774    cx.assert_state_with_diff(both_hunks_expanded.clone());
20775
20776    cx.update_editor(|editor, _, cx| {
20777        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20778    });
20779    executor.run_until_parked();
20780
20781    let first_hunk_expanded = r#"
20782        - a
20783        + ˇA
20784          b
20785          C
20786        "#
20787    .unindent();
20788
20789    cx.assert_state_with_diff(first_hunk_expanded);
20790
20791    cx.update_editor(|editor, _, cx| {
20792        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20793    });
20794    executor.run_until_parked();
20795
20796    cx.assert_state_with_diff(both_hunks_expanded);
20797
20798    cx.set_state(
20799        &r#"
20800        ˇA
20801        b
20802        "#
20803        .unindent(),
20804    );
20805    cx.run_until_parked();
20806
20807    // TODO this cursor position seems bad
20808    cx.assert_state_with_diff(
20809        r#"
20810        - ˇa
20811        + A
20812          b
20813        "#
20814        .unindent(),
20815    );
20816
20817    cx.update_editor(|editor, window, cx| {
20818        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20819    });
20820
20821    cx.assert_state_with_diff(
20822        r#"
20823            - ˇa
20824            + A
20825              b
20826            - c
20827            "#
20828        .unindent(),
20829    );
20830
20831    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20832        let snapshot = editor.snapshot(window, cx);
20833        let hunks = editor
20834            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20835            .collect::<Vec<_>>();
20836        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20837        let buffer_id = hunks[0].buffer_id;
20838        hunks
20839            .into_iter()
20840            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20841            .collect::<Vec<_>>()
20842    });
20843    assert_eq!(hunk_ranges.len(), 2);
20844
20845    cx.update_editor(|editor, _, cx| {
20846        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20847    });
20848    executor.run_until_parked();
20849
20850    cx.assert_state_with_diff(
20851        r#"
20852        - ˇa
20853        + A
20854          b
20855        "#
20856        .unindent(),
20857    );
20858}
20859
20860#[gpui::test]
20861async fn test_toggle_deletion_hunk_at_start_of_file(
20862    executor: BackgroundExecutor,
20863    cx: &mut TestAppContext,
20864) {
20865    init_test(cx, |_| {});
20866    let mut cx = EditorTestContext::new(cx).await;
20867
20868    let diff_base = r#"
20869        a
20870        b
20871        c
20872        "#
20873    .unindent();
20874
20875    cx.set_state(
20876        &r#"
20877        ˇb
20878        c
20879        "#
20880        .unindent(),
20881    );
20882    cx.set_head_text(&diff_base);
20883    cx.update_editor(|editor, window, cx| {
20884        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20885    });
20886    executor.run_until_parked();
20887
20888    let hunk_expanded = r#"
20889        - a
20890          ˇb
20891          c
20892        "#
20893    .unindent();
20894
20895    cx.assert_state_with_diff(hunk_expanded.clone());
20896
20897    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20898        let snapshot = editor.snapshot(window, cx);
20899        let hunks = editor
20900            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20901            .collect::<Vec<_>>();
20902        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20903        let buffer_id = hunks[0].buffer_id;
20904        hunks
20905            .into_iter()
20906            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20907            .collect::<Vec<_>>()
20908    });
20909    assert_eq!(hunk_ranges.len(), 1);
20910
20911    cx.update_editor(|editor, _, cx| {
20912        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20913    });
20914    executor.run_until_parked();
20915
20916    let hunk_collapsed = r#"
20917          ˇb
20918          c
20919        "#
20920    .unindent();
20921
20922    cx.assert_state_with_diff(hunk_collapsed);
20923
20924    cx.update_editor(|editor, _, cx| {
20925        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20926    });
20927    executor.run_until_parked();
20928
20929    cx.assert_state_with_diff(hunk_expanded);
20930}
20931
20932#[gpui::test]
20933async fn test_display_diff_hunks(cx: &mut TestAppContext) {
20934    init_test(cx, |_| {});
20935
20936    let fs = FakeFs::new(cx.executor());
20937    fs.insert_tree(
20938        path!("/test"),
20939        json!({
20940            ".git": {},
20941            "file-1": "ONE\n",
20942            "file-2": "TWO\n",
20943            "file-3": "THREE\n",
20944        }),
20945    )
20946    .await;
20947
20948    fs.set_head_for_repo(
20949        path!("/test/.git").as_ref(),
20950        &[
20951            ("file-1", "one\n".into()),
20952            ("file-2", "two\n".into()),
20953            ("file-3", "three\n".into()),
20954        ],
20955        "deadbeef",
20956    );
20957
20958    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
20959    let mut buffers = vec![];
20960    for i in 1..=3 {
20961        let buffer = project
20962            .update(cx, |project, cx| {
20963                let path = format!(path!("/test/file-{}"), i);
20964                project.open_local_buffer(path, cx)
20965            })
20966            .await
20967            .unwrap();
20968        buffers.push(buffer);
20969    }
20970
20971    let multibuffer = cx.new(|cx| {
20972        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
20973        multibuffer.set_all_diff_hunks_expanded(cx);
20974        for buffer in &buffers {
20975            let snapshot = buffer.read(cx).snapshot();
20976            multibuffer.set_excerpts_for_path(
20977                PathKey::namespaced(
20978                    0,
20979                    buffer.read(cx).file().unwrap().path().as_unix_str().into(),
20980                ),
20981                buffer.clone(),
20982                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
20983                2,
20984                cx,
20985            );
20986        }
20987        multibuffer
20988    });
20989
20990    let editor = cx.add_window(|window, cx| {
20991        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
20992    });
20993    cx.run_until_parked();
20994
20995    let snapshot = editor
20996        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
20997        .unwrap();
20998    let hunks = snapshot
20999        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
21000        .map(|hunk| match hunk {
21001            DisplayDiffHunk::Unfolded {
21002                display_row_range, ..
21003            } => display_row_range,
21004            DisplayDiffHunk::Folded { .. } => unreachable!(),
21005        })
21006        .collect::<Vec<_>>();
21007    assert_eq!(
21008        hunks,
21009        [
21010            DisplayRow(2)..DisplayRow(4),
21011            DisplayRow(7)..DisplayRow(9),
21012            DisplayRow(12)..DisplayRow(14),
21013        ]
21014    );
21015}
21016
21017#[gpui::test]
21018async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
21019    init_test(cx, |_| {});
21020
21021    let mut cx = EditorTestContext::new(cx).await;
21022    cx.set_head_text(indoc! { "
21023        one
21024        two
21025        three
21026        four
21027        five
21028        "
21029    });
21030    cx.set_index_text(indoc! { "
21031        one
21032        two
21033        three
21034        four
21035        five
21036        "
21037    });
21038    cx.set_state(indoc! {"
21039        one
21040        TWO
21041        ˇTHREE
21042        FOUR
21043        five
21044    "});
21045    cx.run_until_parked();
21046    cx.update_editor(|editor, window, cx| {
21047        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21048    });
21049    cx.run_until_parked();
21050    cx.assert_index_text(Some(indoc! {"
21051        one
21052        TWO
21053        THREE
21054        FOUR
21055        five
21056    "}));
21057    cx.set_state(indoc! { "
21058        one
21059        TWO
21060        ˇTHREE-HUNDRED
21061        FOUR
21062        five
21063    "});
21064    cx.run_until_parked();
21065    cx.update_editor(|editor, window, cx| {
21066        let snapshot = editor.snapshot(window, cx);
21067        let hunks = editor
21068            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
21069            .collect::<Vec<_>>();
21070        assert_eq!(hunks.len(), 1);
21071        assert_eq!(
21072            hunks[0].status(),
21073            DiffHunkStatus {
21074                kind: DiffHunkStatusKind::Modified,
21075                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21076            }
21077        );
21078
21079        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21080    });
21081    cx.run_until_parked();
21082    cx.assert_index_text(Some(indoc! {"
21083        one
21084        TWO
21085        THREE-HUNDRED
21086        FOUR
21087        five
21088    "}));
21089}
21090
21091#[gpui::test]
21092fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21093    init_test(cx, |_| {});
21094
21095    let editor = cx.add_window(|window, cx| {
21096        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21097        build_editor(buffer, window, cx)
21098    });
21099
21100    let render_args = Arc::new(Mutex::new(None));
21101    let snapshot = editor
21102        .update(cx, |editor, window, cx| {
21103            let snapshot = editor.buffer().read(cx).snapshot(cx);
21104            let range =
21105                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21106
21107            struct RenderArgs {
21108                row: MultiBufferRow,
21109                folded: bool,
21110                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21111            }
21112
21113            let crease = Crease::inline(
21114                range,
21115                FoldPlaceholder::test(),
21116                {
21117                    let toggle_callback = render_args.clone();
21118                    move |row, folded, callback, _window, _cx| {
21119                        *toggle_callback.lock() = Some(RenderArgs {
21120                            row,
21121                            folded,
21122                            callback,
21123                        });
21124                        div()
21125                    }
21126                },
21127                |_row, _folded, _window, _cx| div(),
21128            );
21129
21130            editor.insert_creases(Some(crease), cx);
21131            let snapshot = editor.snapshot(window, cx);
21132            let _div =
21133                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21134            snapshot
21135        })
21136        .unwrap();
21137
21138    let render_args = render_args.lock().take().unwrap();
21139    assert_eq!(render_args.row, MultiBufferRow(1));
21140    assert!(!render_args.folded);
21141    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21142
21143    cx.update_window(*editor, |_, window, cx| {
21144        (render_args.callback)(true, window, cx)
21145    })
21146    .unwrap();
21147    let snapshot = editor
21148        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21149        .unwrap();
21150    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21151
21152    cx.update_window(*editor, |_, window, cx| {
21153        (render_args.callback)(false, window, cx)
21154    })
21155    .unwrap();
21156    let snapshot = editor
21157        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21158        .unwrap();
21159    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21160}
21161
21162#[gpui::test]
21163async fn test_input_text(cx: &mut TestAppContext) {
21164    init_test(cx, |_| {});
21165    let mut cx = EditorTestContext::new(cx).await;
21166
21167    cx.set_state(
21168        &r#"ˇone
21169        two
21170
21171        three
21172        fourˇ
21173        five
21174
21175        siˇx"#
21176            .unindent(),
21177    );
21178
21179    cx.dispatch_action(HandleInput(String::new()));
21180    cx.assert_editor_state(
21181        &r#"ˇone
21182        two
21183
21184        three
21185        fourˇ
21186        five
21187
21188        siˇx"#
21189            .unindent(),
21190    );
21191
21192    cx.dispatch_action(HandleInput("AAAA".to_string()));
21193    cx.assert_editor_state(
21194        &r#"AAAAˇone
21195        two
21196
21197        three
21198        fourAAAAˇ
21199        five
21200
21201        siAAAAˇx"#
21202            .unindent(),
21203    );
21204}
21205
21206#[gpui::test]
21207async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
21208    init_test(cx, |_| {});
21209
21210    let mut cx = EditorTestContext::new(cx).await;
21211    cx.set_state(
21212        r#"let foo = 1;
21213let foo = 2;
21214let foo = 3;
21215let fooˇ = 4;
21216let foo = 5;
21217let foo = 6;
21218let foo = 7;
21219let foo = 8;
21220let foo = 9;
21221let foo = 10;
21222let foo = 11;
21223let foo = 12;
21224let foo = 13;
21225let foo = 14;
21226let foo = 15;"#,
21227    );
21228
21229    cx.update_editor(|e, window, cx| {
21230        assert_eq!(
21231            e.next_scroll_position,
21232            NextScrollCursorCenterTopBottom::Center,
21233            "Default next scroll direction is center",
21234        );
21235
21236        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21237        assert_eq!(
21238            e.next_scroll_position,
21239            NextScrollCursorCenterTopBottom::Top,
21240            "After center, next scroll direction should be top",
21241        );
21242
21243        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21244        assert_eq!(
21245            e.next_scroll_position,
21246            NextScrollCursorCenterTopBottom::Bottom,
21247            "After top, next scroll direction should be bottom",
21248        );
21249
21250        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21251        assert_eq!(
21252            e.next_scroll_position,
21253            NextScrollCursorCenterTopBottom::Center,
21254            "After bottom, scrolling should start over",
21255        );
21256
21257        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21258        assert_eq!(
21259            e.next_scroll_position,
21260            NextScrollCursorCenterTopBottom::Top,
21261            "Scrolling continues if retriggered fast enough"
21262        );
21263    });
21264
21265    cx.executor()
21266        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
21267    cx.executor().run_until_parked();
21268    cx.update_editor(|e, _, _| {
21269        assert_eq!(
21270            e.next_scroll_position,
21271            NextScrollCursorCenterTopBottom::Center,
21272            "If scrolling is not triggered fast enough, it should reset"
21273        );
21274    });
21275}
21276
21277#[gpui::test]
21278async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
21279    init_test(cx, |_| {});
21280    let mut cx = EditorLspTestContext::new_rust(
21281        lsp::ServerCapabilities {
21282            definition_provider: Some(lsp::OneOf::Left(true)),
21283            references_provider: Some(lsp::OneOf::Left(true)),
21284            ..lsp::ServerCapabilities::default()
21285        },
21286        cx,
21287    )
21288    .await;
21289
21290    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21291        let go_to_definition = cx
21292            .lsp
21293            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21294                move |params, _| async move {
21295                    if empty_go_to_definition {
21296                        Ok(None)
21297                    } else {
21298                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21299                            uri: params.text_document_position_params.text_document.uri,
21300                            range: lsp::Range::new(
21301                                lsp::Position::new(4, 3),
21302                                lsp::Position::new(4, 6),
21303                            ),
21304                        })))
21305                    }
21306                },
21307            );
21308        let references = cx
21309            .lsp
21310            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21311                Ok(Some(vec![lsp::Location {
21312                    uri: params.text_document_position.text_document.uri,
21313                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21314                }]))
21315            });
21316        (go_to_definition, references)
21317    };
21318
21319    cx.set_state(
21320        &r#"fn one() {
21321            let mut a = ˇtwo();
21322        }
21323
21324        fn two() {}"#
21325            .unindent(),
21326    );
21327    set_up_lsp_handlers(false, &mut cx);
21328    let navigated = cx
21329        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21330        .await
21331        .expect("Failed to navigate to definition");
21332    assert_eq!(
21333        navigated,
21334        Navigated::Yes,
21335        "Should have navigated to definition from the GetDefinition response"
21336    );
21337    cx.assert_editor_state(
21338        &r#"fn one() {
21339            let mut a = two();
21340        }
21341
21342        fn «twoˇ»() {}"#
21343            .unindent(),
21344    );
21345
21346    let editors = cx.update_workspace(|workspace, _, cx| {
21347        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21348    });
21349    cx.update_editor(|_, _, test_editor_cx| {
21350        assert_eq!(
21351            editors.len(),
21352            1,
21353            "Initially, only one, test, editor should be open in the workspace"
21354        );
21355        assert_eq!(
21356            test_editor_cx.entity(),
21357            editors.last().expect("Asserted len is 1").clone()
21358        );
21359    });
21360
21361    set_up_lsp_handlers(true, &mut cx);
21362    let navigated = cx
21363        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21364        .await
21365        .expect("Failed to navigate to lookup references");
21366    assert_eq!(
21367        navigated,
21368        Navigated::Yes,
21369        "Should have navigated to references as a fallback after empty GoToDefinition response"
21370    );
21371    // We should not change the selections in the existing file,
21372    // if opening another milti buffer with the references
21373    cx.assert_editor_state(
21374        &r#"fn one() {
21375            let mut a = two();
21376        }
21377
21378        fn «twoˇ»() {}"#
21379            .unindent(),
21380    );
21381    let editors = cx.update_workspace(|workspace, _, cx| {
21382        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21383    });
21384    cx.update_editor(|_, _, test_editor_cx| {
21385        assert_eq!(
21386            editors.len(),
21387            2,
21388            "After falling back to references search, we open a new editor with the results"
21389        );
21390        let references_fallback_text = editors
21391            .into_iter()
21392            .find(|new_editor| *new_editor != test_editor_cx.entity())
21393            .expect("Should have one non-test editor now")
21394            .read(test_editor_cx)
21395            .text(test_editor_cx);
21396        assert_eq!(
21397            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21398            "Should use the range from the references response and not the GoToDefinition one"
21399        );
21400    });
21401}
21402
21403#[gpui::test]
21404async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21405    init_test(cx, |_| {});
21406    cx.update(|cx| {
21407        let mut editor_settings = EditorSettings::get_global(cx).clone();
21408        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21409        EditorSettings::override_global(editor_settings, cx);
21410    });
21411    let mut cx = EditorLspTestContext::new_rust(
21412        lsp::ServerCapabilities {
21413            definition_provider: Some(lsp::OneOf::Left(true)),
21414            references_provider: Some(lsp::OneOf::Left(true)),
21415            ..lsp::ServerCapabilities::default()
21416        },
21417        cx,
21418    )
21419    .await;
21420    let original_state = r#"fn one() {
21421        let mut a = ˇtwo();
21422    }
21423
21424    fn two() {}"#
21425        .unindent();
21426    cx.set_state(&original_state);
21427
21428    let mut go_to_definition = cx
21429        .lsp
21430        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21431            move |_, _| async move { Ok(None) },
21432        );
21433    let _references = cx
21434        .lsp
21435        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21436            panic!("Should not call for references with no go to definition fallback")
21437        });
21438
21439    let navigated = cx
21440        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21441        .await
21442        .expect("Failed to navigate to lookup references");
21443    go_to_definition
21444        .next()
21445        .await
21446        .expect("Should have called the go_to_definition handler");
21447
21448    assert_eq!(
21449        navigated,
21450        Navigated::No,
21451        "Should have navigated to references as a fallback after empty GoToDefinition response"
21452    );
21453    cx.assert_editor_state(&original_state);
21454    let editors = cx.update_workspace(|workspace, _, cx| {
21455        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21456    });
21457    cx.update_editor(|_, _, _| {
21458        assert_eq!(
21459            editors.len(),
21460            1,
21461            "After unsuccessful fallback, no other editor should have been opened"
21462        );
21463    });
21464}
21465
21466#[gpui::test]
21467async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
21468    init_test(cx, |_| {});
21469    let mut cx = EditorLspTestContext::new_rust(
21470        lsp::ServerCapabilities {
21471            references_provider: Some(lsp::OneOf::Left(true)),
21472            ..lsp::ServerCapabilities::default()
21473        },
21474        cx,
21475    )
21476    .await;
21477
21478    cx.set_state(
21479        &r#"
21480        fn one() {
21481            let mut a = two();
21482        }
21483
21484        fn ˇtwo() {}"#
21485            .unindent(),
21486    );
21487    cx.lsp
21488        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21489            Ok(Some(vec![
21490                lsp::Location {
21491                    uri: params.text_document_position.text_document.uri.clone(),
21492                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21493                },
21494                lsp::Location {
21495                    uri: params.text_document_position.text_document.uri,
21496                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
21497                },
21498            ]))
21499        });
21500    let navigated = cx
21501        .update_editor(|editor, window, cx| {
21502            editor.find_all_references(&FindAllReferences, window, cx)
21503        })
21504        .unwrap()
21505        .await
21506        .expect("Failed to navigate to references");
21507    assert_eq!(
21508        navigated,
21509        Navigated::Yes,
21510        "Should have navigated to references from the FindAllReferences response"
21511    );
21512    cx.assert_editor_state(
21513        &r#"fn one() {
21514            let mut a = two();
21515        }
21516
21517        fn ˇtwo() {}"#
21518            .unindent(),
21519    );
21520
21521    let editors = cx.update_workspace(|workspace, _, cx| {
21522        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21523    });
21524    cx.update_editor(|_, _, _| {
21525        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
21526    });
21527
21528    cx.set_state(
21529        &r#"fn one() {
21530            let mut a = ˇtwo();
21531        }
21532
21533        fn two() {}"#
21534            .unindent(),
21535    );
21536    let navigated = cx
21537        .update_editor(|editor, window, cx| {
21538            editor.find_all_references(&FindAllReferences, window, cx)
21539        })
21540        .unwrap()
21541        .await
21542        .expect("Failed to navigate to references");
21543    assert_eq!(
21544        navigated,
21545        Navigated::Yes,
21546        "Should have navigated to references from the FindAllReferences response"
21547    );
21548    cx.assert_editor_state(
21549        &r#"fn one() {
21550            let mut a = ˇtwo();
21551        }
21552
21553        fn two() {}"#
21554            .unindent(),
21555    );
21556    let editors = cx.update_workspace(|workspace, _, cx| {
21557        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21558    });
21559    cx.update_editor(|_, _, _| {
21560        assert_eq!(
21561            editors.len(),
21562            2,
21563            "should have re-used the previous multibuffer"
21564        );
21565    });
21566
21567    cx.set_state(
21568        &r#"fn one() {
21569            let mut a = ˇtwo();
21570        }
21571        fn three() {}
21572        fn two() {}"#
21573            .unindent(),
21574    );
21575    cx.lsp
21576        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21577            Ok(Some(vec![
21578                lsp::Location {
21579                    uri: params.text_document_position.text_document.uri.clone(),
21580                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21581                },
21582                lsp::Location {
21583                    uri: params.text_document_position.text_document.uri,
21584                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
21585                },
21586            ]))
21587        });
21588    let navigated = cx
21589        .update_editor(|editor, window, cx| {
21590            editor.find_all_references(&FindAllReferences, window, cx)
21591        })
21592        .unwrap()
21593        .await
21594        .expect("Failed to navigate to references");
21595    assert_eq!(
21596        navigated,
21597        Navigated::Yes,
21598        "Should have navigated to references from the FindAllReferences response"
21599    );
21600    cx.assert_editor_state(
21601        &r#"fn one() {
21602                let mut a = ˇtwo();
21603            }
21604            fn three() {}
21605            fn two() {}"#
21606            .unindent(),
21607    );
21608    let editors = cx.update_workspace(|workspace, _, cx| {
21609        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21610    });
21611    cx.update_editor(|_, _, _| {
21612        assert_eq!(
21613            editors.len(),
21614            3,
21615            "should have used a new multibuffer as offsets changed"
21616        );
21617    });
21618}
21619#[gpui::test]
21620async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
21621    init_test(cx, |_| {});
21622
21623    let language = Arc::new(Language::new(
21624        LanguageConfig::default(),
21625        Some(tree_sitter_rust::LANGUAGE.into()),
21626    ));
21627
21628    let text = r#"
21629        #[cfg(test)]
21630        mod tests() {
21631            #[test]
21632            fn runnable_1() {
21633                let a = 1;
21634            }
21635
21636            #[test]
21637            fn runnable_2() {
21638                let a = 1;
21639                let b = 2;
21640            }
21641        }
21642    "#
21643    .unindent();
21644
21645    let fs = FakeFs::new(cx.executor());
21646    fs.insert_file("/file.rs", Default::default()).await;
21647
21648    let project = Project::test(fs, ["/a".as_ref()], cx).await;
21649    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21650    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21651    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
21652    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
21653
21654    let editor = cx.new_window_entity(|window, cx| {
21655        Editor::new(
21656            EditorMode::full(),
21657            multi_buffer,
21658            Some(project.clone()),
21659            window,
21660            cx,
21661        )
21662    });
21663
21664    editor.update_in(cx, |editor, window, cx| {
21665        let snapshot = editor.buffer().read(cx).snapshot(cx);
21666        editor.tasks.insert(
21667            (buffer.read(cx).remote_id(), 3),
21668            RunnableTasks {
21669                templates: vec![],
21670                offset: snapshot.anchor_before(43),
21671                column: 0,
21672                extra_variables: HashMap::default(),
21673                context_range: BufferOffset(43)..BufferOffset(85),
21674            },
21675        );
21676        editor.tasks.insert(
21677            (buffer.read(cx).remote_id(), 8),
21678            RunnableTasks {
21679                templates: vec![],
21680                offset: snapshot.anchor_before(86),
21681                column: 0,
21682                extra_variables: HashMap::default(),
21683                context_range: BufferOffset(86)..BufferOffset(191),
21684            },
21685        );
21686
21687        // Test finding task when cursor is inside function body
21688        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21689            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
21690        });
21691        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21692        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
21693
21694        // Test finding task when cursor is on function name
21695        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21696            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
21697        });
21698        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21699        assert_eq!(row, 8, "Should find task when cursor is on function name");
21700    });
21701}
21702
21703#[gpui::test]
21704async fn test_folding_buffers(cx: &mut TestAppContext) {
21705    init_test(cx, |_| {});
21706
21707    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21708    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
21709    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
21710
21711    let fs = FakeFs::new(cx.executor());
21712    fs.insert_tree(
21713        path!("/a"),
21714        json!({
21715            "first.rs": sample_text_1,
21716            "second.rs": sample_text_2,
21717            "third.rs": sample_text_3,
21718        }),
21719    )
21720    .await;
21721    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21722    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21723    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21724    let worktree = project.update(cx, |project, cx| {
21725        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21726        assert_eq!(worktrees.len(), 1);
21727        worktrees.pop().unwrap()
21728    });
21729    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21730
21731    let buffer_1 = project
21732        .update(cx, |project, cx| {
21733            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
21734        })
21735        .await
21736        .unwrap();
21737    let buffer_2 = project
21738        .update(cx, |project, cx| {
21739            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
21740        })
21741        .await
21742        .unwrap();
21743    let buffer_3 = project
21744        .update(cx, |project, cx| {
21745            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
21746        })
21747        .await
21748        .unwrap();
21749
21750    let multi_buffer = cx.new(|cx| {
21751        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21752        multi_buffer.push_excerpts(
21753            buffer_1.clone(),
21754            [
21755                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21756                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21757                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21758            ],
21759            cx,
21760        );
21761        multi_buffer.push_excerpts(
21762            buffer_2.clone(),
21763            [
21764                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21765                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21766                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21767            ],
21768            cx,
21769        );
21770        multi_buffer.push_excerpts(
21771            buffer_3.clone(),
21772            [
21773                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21774                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21775                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21776            ],
21777            cx,
21778        );
21779        multi_buffer
21780    });
21781    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21782        Editor::new(
21783            EditorMode::full(),
21784            multi_buffer.clone(),
21785            Some(project.clone()),
21786            window,
21787            cx,
21788        )
21789    });
21790
21791    assert_eq!(
21792        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21793        "\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",
21794    );
21795
21796    multi_buffer_editor.update(cx, |editor, cx| {
21797        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21798    });
21799    assert_eq!(
21800        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21801        "\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",
21802        "After folding the first buffer, its text should not be displayed"
21803    );
21804
21805    multi_buffer_editor.update(cx, |editor, cx| {
21806        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21807    });
21808    assert_eq!(
21809        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21810        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
21811        "After folding the second buffer, its text should not be displayed"
21812    );
21813
21814    multi_buffer_editor.update(cx, |editor, cx| {
21815        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21816    });
21817    assert_eq!(
21818        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21819        "\n\n\n\n\n",
21820        "After folding the third buffer, its text should not be displayed"
21821    );
21822
21823    // Emulate selection inside the fold logic, that should work
21824    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21825        editor
21826            .snapshot(window, cx)
21827            .next_line_boundary(Point::new(0, 4));
21828    });
21829
21830    multi_buffer_editor.update(cx, |editor, cx| {
21831        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21832    });
21833    assert_eq!(
21834        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21835        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21836        "After unfolding the second buffer, its text should be displayed"
21837    );
21838
21839    // Typing inside of buffer 1 causes that buffer to be unfolded.
21840    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21841        assert_eq!(
21842            multi_buffer
21843                .read(cx)
21844                .snapshot(cx)
21845                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
21846                .collect::<String>(),
21847            "bbbb"
21848        );
21849        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21850            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
21851        });
21852        editor.handle_input("B", window, cx);
21853    });
21854
21855    assert_eq!(
21856        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21857        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21858        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
21859    );
21860
21861    multi_buffer_editor.update(cx, |editor, cx| {
21862        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21863    });
21864    assert_eq!(
21865        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21866        "\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",
21867        "After unfolding the all buffers, all original text should be displayed"
21868    );
21869}
21870
21871#[gpui::test]
21872async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
21873    init_test(cx, |_| {});
21874
21875    let sample_text_1 = "1111\n2222\n3333".to_string();
21876    let sample_text_2 = "4444\n5555\n6666".to_string();
21877    let sample_text_3 = "7777\n8888\n9999".to_string();
21878
21879    let fs = FakeFs::new(cx.executor());
21880    fs.insert_tree(
21881        path!("/a"),
21882        json!({
21883            "first.rs": sample_text_1,
21884            "second.rs": sample_text_2,
21885            "third.rs": sample_text_3,
21886        }),
21887    )
21888    .await;
21889    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21890    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21891    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21892    let worktree = project.update(cx, |project, cx| {
21893        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21894        assert_eq!(worktrees.len(), 1);
21895        worktrees.pop().unwrap()
21896    });
21897    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21898
21899    let buffer_1 = project
21900        .update(cx, |project, cx| {
21901            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
21902        })
21903        .await
21904        .unwrap();
21905    let buffer_2 = project
21906        .update(cx, |project, cx| {
21907            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
21908        })
21909        .await
21910        .unwrap();
21911    let buffer_3 = project
21912        .update(cx, |project, cx| {
21913            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
21914        })
21915        .await
21916        .unwrap();
21917
21918    let multi_buffer = cx.new(|cx| {
21919        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21920        multi_buffer.push_excerpts(
21921            buffer_1.clone(),
21922            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21923            cx,
21924        );
21925        multi_buffer.push_excerpts(
21926            buffer_2.clone(),
21927            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21928            cx,
21929        );
21930        multi_buffer.push_excerpts(
21931            buffer_3.clone(),
21932            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21933            cx,
21934        );
21935        multi_buffer
21936    });
21937
21938    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21939        Editor::new(
21940            EditorMode::full(),
21941            multi_buffer,
21942            Some(project.clone()),
21943            window,
21944            cx,
21945        )
21946    });
21947
21948    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
21949    assert_eq!(
21950        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21951        full_text,
21952    );
21953
21954    multi_buffer_editor.update(cx, |editor, cx| {
21955        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21956    });
21957    assert_eq!(
21958        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21959        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
21960        "After folding the first buffer, its text should not be displayed"
21961    );
21962
21963    multi_buffer_editor.update(cx, |editor, cx| {
21964        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21965    });
21966
21967    assert_eq!(
21968        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21969        "\n\n\n\n\n\n7777\n8888\n9999",
21970        "After folding the second buffer, its text should not be displayed"
21971    );
21972
21973    multi_buffer_editor.update(cx, |editor, cx| {
21974        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21975    });
21976    assert_eq!(
21977        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21978        "\n\n\n\n\n",
21979        "After folding the third buffer, its text should not be displayed"
21980    );
21981
21982    multi_buffer_editor.update(cx, |editor, cx| {
21983        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21984    });
21985    assert_eq!(
21986        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21987        "\n\n\n\n4444\n5555\n6666\n\n",
21988        "After unfolding the second buffer, its text should be displayed"
21989    );
21990
21991    multi_buffer_editor.update(cx, |editor, cx| {
21992        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
21993    });
21994    assert_eq!(
21995        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21996        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
21997        "After unfolding the first buffer, its text should be displayed"
21998    );
21999
22000    multi_buffer_editor.update(cx, |editor, cx| {
22001        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22002    });
22003    assert_eq!(
22004        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22005        full_text,
22006        "After unfolding all buffers, all original text should be displayed"
22007    );
22008}
22009
22010#[gpui::test]
22011async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
22012    init_test(cx, |_| {});
22013
22014    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22015
22016    let fs = FakeFs::new(cx.executor());
22017    fs.insert_tree(
22018        path!("/a"),
22019        json!({
22020            "main.rs": sample_text,
22021        }),
22022    )
22023    .await;
22024    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22025    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22026    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22027    let worktree = project.update(cx, |project, cx| {
22028        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22029        assert_eq!(worktrees.len(), 1);
22030        worktrees.pop().unwrap()
22031    });
22032    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22033
22034    let buffer_1 = project
22035        .update(cx, |project, cx| {
22036            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22037        })
22038        .await
22039        .unwrap();
22040
22041    let multi_buffer = cx.new(|cx| {
22042        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22043        multi_buffer.push_excerpts(
22044            buffer_1.clone(),
22045            [ExcerptRange::new(
22046                Point::new(0, 0)
22047                    ..Point::new(
22048                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
22049                        0,
22050                    ),
22051            )],
22052            cx,
22053        );
22054        multi_buffer
22055    });
22056    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22057        Editor::new(
22058            EditorMode::full(),
22059            multi_buffer,
22060            Some(project.clone()),
22061            window,
22062            cx,
22063        )
22064    });
22065
22066    let selection_range = Point::new(1, 0)..Point::new(2, 0);
22067    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22068        enum TestHighlight {}
22069        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22070        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
22071        editor.highlight_text::<TestHighlight>(
22072            vec![highlight_range.clone()],
22073            HighlightStyle::color(Hsla::green()),
22074            cx,
22075        );
22076        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22077            s.select_ranges(Some(highlight_range))
22078        });
22079    });
22080
22081    let full_text = format!("\n\n{sample_text}");
22082    assert_eq!(
22083        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22084        full_text,
22085    );
22086}
22087
22088#[gpui::test]
22089async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22090    init_test(cx, |_| {});
22091    cx.update(|cx| {
22092        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22093            "keymaps/default-linux.json",
22094            cx,
22095        )
22096        .unwrap();
22097        cx.bind_keys(default_key_bindings);
22098    });
22099
22100    let (editor, cx) = cx.add_window_view(|window, cx| {
22101        let multi_buffer = MultiBuffer::build_multi(
22102            [
22103                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22104                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22105                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22106                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22107            ],
22108            cx,
22109        );
22110        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22111
22112        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22113        // fold all but the second buffer, so that we test navigating between two
22114        // adjacent folded buffers, as well as folded buffers at the start and
22115        // end the multibuffer
22116        editor.fold_buffer(buffer_ids[0], cx);
22117        editor.fold_buffer(buffer_ids[2], cx);
22118        editor.fold_buffer(buffer_ids[3], cx);
22119
22120        editor
22121    });
22122    cx.simulate_resize(size(px(1000.), px(1000.)));
22123
22124    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22125    cx.assert_excerpts_with_selections(indoc! {"
22126        [EXCERPT]
22127        ˇ[FOLDED]
22128        [EXCERPT]
22129        a1
22130        b1
22131        [EXCERPT]
22132        [FOLDED]
22133        [EXCERPT]
22134        [FOLDED]
22135        "
22136    });
22137    cx.simulate_keystroke("down");
22138    cx.assert_excerpts_with_selections(indoc! {"
22139        [EXCERPT]
22140        [FOLDED]
22141        [EXCERPT]
22142        ˇa1
22143        b1
22144        [EXCERPT]
22145        [FOLDED]
22146        [EXCERPT]
22147        [FOLDED]
22148        "
22149    });
22150    cx.simulate_keystroke("down");
22151    cx.assert_excerpts_with_selections(indoc! {"
22152        [EXCERPT]
22153        [FOLDED]
22154        [EXCERPT]
22155        a1
22156        ˇb1
22157        [EXCERPT]
22158        [FOLDED]
22159        [EXCERPT]
22160        [FOLDED]
22161        "
22162    });
22163    cx.simulate_keystroke("down");
22164    cx.assert_excerpts_with_selections(indoc! {"
22165        [EXCERPT]
22166        [FOLDED]
22167        [EXCERPT]
22168        a1
22169        b1
22170        ˇ[EXCERPT]
22171        [FOLDED]
22172        [EXCERPT]
22173        [FOLDED]
22174        "
22175    });
22176    cx.simulate_keystroke("down");
22177    cx.assert_excerpts_with_selections(indoc! {"
22178        [EXCERPT]
22179        [FOLDED]
22180        [EXCERPT]
22181        a1
22182        b1
22183        [EXCERPT]
22184        ˇ[FOLDED]
22185        [EXCERPT]
22186        [FOLDED]
22187        "
22188    });
22189    for _ in 0..5 {
22190        cx.simulate_keystroke("down");
22191        cx.assert_excerpts_with_selections(indoc! {"
22192            [EXCERPT]
22193            [FOLDED]
22194            [EXCERPT]
22195            a1
22196            b1
22197            [EXCERPT]
22198            [FOLDED]
22199            [EXCERPT]
22200            ˇ[FOLDED]
22201            "
22202        });
22203    }
22204
22205    cx.simulate_keystroke("up");
22206    cx.assert_excerpts_with_selections(indoc! {"
22207        [EXCERPT]
22208        [FOLDED]
22209        [EXCERPT]
22210        a1
22211        b1
22212        [EXCERPT]
22213        ˇ[FOLDED]
22214        [EXCERPT]
22215        [FOLDED]
22216        "
22217    });
22218    cx.simulate_keystroke("up");
22219    cx.assert_excerpts_with_selections(indoc! {"
22220        [EXCERPT]
22221        [FOLDED]
22222        [EXCERPT]
22223        a1
22224        b1
22225        ˇ[EXCERPT]
22226        [FOLDED]
22227        [EXCERPT]
22228        [FOLDED]
22229        "
22230    });
22231    cx.simulate_keystroke("up");
22232    cx.assert_excerpts_with_selections(indoc! {"
22233        [EXCERPT]
22234        [FOLDED]
22235        [EXCERPT]
22236        a1
22237        ˇb1
22238        [EXCERPT]
22239        [FOLDED]
22240        [EXCERPT]
22241        [FOLDED]
22242        "
22243    });
22244    cx.simulate_keystroke("up");
22245    cx.assert_excerpts_with_selections(indoc! {"
22246        [EXCERPT]
22247        [FOLDED]
22248        [EXCERPT]
22249        ˇa1
22250        b1
22251        [EXCERPT]
22252        [FOLDED]
22253        [EXCERPT]
22254        [FOLDED]
22255        "
22256    });
22257    for _ in 0..5 {
22258        cx.simulate_keystroke("up");
22259        cx.assert_excerpts_with_selections(indoc! {"
22260            [EXCERPT]
22261            ˇ[FOLDED]
22262            [EXCERPT]
22263            a1
22264            b1
22265            [EXCERPT]
22266            [FOLDED]
22267            [EXCERPT]
22268            [FOLDED]
22269            "
22270        });
22271    }
22272}
22273
22274#[gpui::test]
22275async fn test_edit_prediction_text(cx: &mut TestAppContext) {
22276    init_test(cx, |_| {});
22277
22278    // Simple insertion
22279    assert_highlighted_edits(
22280        "Hello, world!",
22281        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
22282        true,
22283        cx,
22284        |highlighted_edits, cx| {
22285            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
22286            assert_eq!(highlighted_edits.highlights.len(), 1);
22287            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
22288            assert_eq!(
22289                highlighted_edits.highlights[0].1.background_color,
22290                Some(cx.theme().status().created_background)
22291            );
22292        },
22293    )
22294    .await;
22295
22296    // Replacement
22297    assert_highlighted_edits(
22298        "This is a test.",
22299        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
22300        false,
22301        cx,
22302        |highlighted_edits, cx| {
22303            assert_eq!(highlighted_edits.text, "That is a test.");
22304            assert_eq!(highlighted_edits.highlights.len(), 1);
22305            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
22306            assert_eq!(
22307                highlighted_edits.highlights[0].1.background_color,
22308                Some(cx.theme().status().created_background)
22309            );
22310        },
22311    )
22312    .await;
22313
22314    // Multiple edits
22315    assert_highlighted_edits(
22316        "Hello, world!",
22317        vec![
22318            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
22319            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
22320        ],
22321        false,
22322        cx,
22323        |highlighted_edits, cx| {
22324            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
22325            assert_eq!(highlighted_edits.highlights.len(), 2);
22326            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
22327            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
22328            assert_eq!(
22329                highlighted_edits.highlights[0].1.background_color,
22330                Some(cx.theme().status().created_background)
22331            );
22332            assert_eq!(
22333                highlighted_edits.highlights[1].1.background_color,
22334                Some(cx.theme().status().created_background)
22335            );
22336        },
22337    )
22338    .await;
22339
22340    // Multiple lines with edits
22341    assert_highlighted_edits(
22342        "First line\nSecond line\nThird line\nFourth line",
22343        vec![
22344            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
22345            (
22346                Point::new(2, 0)..Point::new(2, 10),
22347                "New third line".to_string(),
22348            ),
22349            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
22350        ],
22351        false,
22352        cx,
22353        |highlighted_edits, cx| {
22354            assert_eq!(
22355                highlighted_edits.text,
22356                "Second modified\nNew third line\nFourth updated line"
22357            );
22358            assert_eq!(highlighted_edits.highlights.len(), 3);
22359            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
22360            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
22361            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
22362            for highlight in &highlighted_edits.highlights {
22363                assert_eq!(
22364                    highlight.1.background_color,
22365                    Some(cx.theme().status().created_background)
22366                );
22367            }
22368        },
22369    )
22370    .await;
22371}
22372
22373#[gpui::test]
22374async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
22375    init_test(cx, |_| {});
22376
22377    // Deletion
22378    assert_highlighted_edits(
22379        "Hello, world!",
22380        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
22381        true,
22382        cx,
22383        |highlighted_edits, cx| {
22384            assert_eq!(highlighted_edits.text, "Hello, world!");
22385            assert_eq!(highlighted_edits.highlights.len(), 1);
22386            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
22387            assert_eq!(
22388                highlighted_edits.highlights[0].1.background_color,
22389                Some(cx.theme().status().deleted_background)
22390            );
22391        },
22392    )
22393    .await;
22394
22395    // Insertion
22396    assert_highlighted_edits(
22397        "Hello, world!",
22398        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
22399        true,
22400        cx,
22401        |highlighted_edits, cx| {
22402            assert_eq!(highlighted_edits.highlights.len(), 1);
22403            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
22404            assert_eq!(
22405                highlighted_edits.highlights[0].1.background_color,
22406                Some(cx.theme().status().created_background)
22407            );
22408        },
22409    )
22410    .await;
22411}
22412
22413async fn assert_highlighted_edits(
22414    text: &str,
22415    edits: Vec<(Range<Point>, String)>,
22416    include_deletions: bool,
22417    cx: &mut TestAppContext,
22418    assertion_fn: impl Fn(HighlightedText, &App),
22419) {
22420    let window = cx.add_window(|window, cx| {
22421        let buffer = MultiBuffer::build_simple(text, cx);
22422        Editor::new(EditorMode::full(), buffer, None, window, cx)
22423    });
22424    let cx = &mut VisualTestContext::from_window(*window, cx);
22425
22426    let (buffer, snapshot) = window
22427        .update(cx, |editor, _window, cx| {
22428            (
22429                editor.buffer().clone(),
22430                editor.buffer().read(cx).snapshot(cx),
22431            )
22432        })
22433        .unwrap();
22434
22435    let edits = edits
22436        .into_iter()
22437        .map(|(range, edit)| {
22438            (
22439                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
22440                edit,
22441            )
22442        })
22443        .collect::<Vec<_>>();
22444
22445    let text_anchor_edits = edits
22446        .clone()
22447        .into_iter()
22448        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
22449        .collect::<Vec<_>>();
22450
22451    let edit_preview = window
22452        .update(cx, |_, _window, cx| {
22453            buffer
22454                .read(cx)
22455                .as_singleton()
22456                .unwrap()
22457                .read(cx)
22458                .preview_edits(text_anchor_edits.into(), cx)
22459        })
22460        .unwrap()
22461        .await;
22462
22463    cx.update(|_window, cx| {
22464        let highlighted_edits = edit_prediction_edit_text(
22465            snapshot.as_singleton().unwrap().2,
22466            &edits,
22467            &edit_preview,
22468            include_deletions,
22469            cx,
22470        );
22471        assertion_fn(highlighted_edits, cx)
22472    });
22473}
22474
22475#[track_caller]
22476fn assert_breakpoint(
22477    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
22478    path: &Arc<Path>,
22479    expected: Vec<(u32, Breakpoint)>,
22480) {
22481    if expected.is_empty() {
22482        assert!(!breakpoints.contains_key(path), "{}", path.display());
22483    } else {
22484        let mut breakpoint = breakpoints
22485            .get(path)
22486            .unwrap()
22487            .iter()
22488            .map(|breakpoint| {
22489                (
22490                    breakpoint.row,
22491                    Breakpoint {
22492                        message: breakpoint.message.clone(),
22493                        state: breakpoint.state,
22494                        condition: breakpoint.condition.clone(),
22495                        hit_condition: breakpoint.hit_condition.clone(),
22496                    },
22497                )
22498            })
22499            .collect::<Vec<_>>();
22500
22501        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
22502
22503        assert_eq!(expected, breakpoint);
22504    }
22505}
22506
22507fn add_log_breakpoint_at_cursor(
22508    editor: &mut Editor,
22509    log_message: &str,
22510    window: &mut Window,
22511    cx: &mut Context<Editor>,
22512) {
22513    let (anchor, bp) = editor
22514        .breakpoints_at_cursors(window, cx)
22515        .first()
22516        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
22517        .unwrap_or_else(|| {
22518            let cursor_position: Point = editor.selections.newest(cx).head();
22519
22520            let breakpoint_position = editor
22521                .snapshot(window, cx)
22522                .display_snapshot
22523                .buffer_snapshot
22524                .anchor_before(Point::new(cursor_position.row, 0));
22525
22526            (breakpoint_position, Breakpoint::new_log(log_message))
22527        });
22528
22529    editor.edit_breakpoint_at_anchor(
22530        anchor,
22531        bp,
22532        BreakpointEditAction::EditLogMessage(log_message.into()),
22533        cx,
22534    );
22535}
22536
22537#[gpui::test]
22538async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
22539    init_test(cx, |_| {});
22540
22541    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22542    let fs = FakeFs::new(cx.executor());
22543    fs.insert_tree(
22544        path!("/a"),
22545        json!({
22546            "main.rs": sample_text,
22547        }),
22548    )
22549    .await;
22550    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22551    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22552    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22553
22554    let fs = FakeFs::new(cx.executor());
22555    fs.insert_tree(
22556        path!("/a"),
22557        json!({
22558            "main.rs": sample_text,
22559        }),
22560    )
22561    .await;
22562    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22563    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22564    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22565    let worktree_id = workspace
22566        .update(cx, |workspace, _window, cx| {
22567            workspace.project().update(cx, |project, cx| {
22568                project.worktrees(cx).next().unwrap().read(cx).id()
22569            })
22570        })
22571        .unwrap();
22572
22573    let buffer = project
22574        .update(cx, |project, cx| {
22575            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22576        })
22577        .await
22578        .unwrap();
22579
22580    let (editor, cx) = cx.add_window_view(|window, cx| {
22581        Editor::new(
22582            EditorMode::full(),
22583            MultiBuffer::build_from_buffer(buffer, cx),
22584            Some(project.clone()),
22585            window,
22586            cx,
22587        )
22588    });
22589
22590    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22591    let abs_path = project.read_with(cx, |project, cx| {
22592        project
22593            .absolute_path(&project_path, cx)
22594            .map(Arc::from)
22595            .unwrap()
22596    });
22597
22598    // assert we can add breakpoint on the first line
22599    editor.update_in(cx, |editor, window, cx| {
22600        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22601        editor.move_to_end(&MoveToEnd, window, cx);
22602        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22603    });
22604
22605    let breakpoints = editor.update(cx, |editor, cx| {
22606        editor
22607            .breakpoint_store()
22608            .as_ref()
22609            .unwrap()
22610            .read(cx)
22611            .all_source_breakpoints(cx)
22612    });
22613
22614    assert_eq!(1, breakpoints.len());
22615    assert_breakpoint(
22616        &breakpoints,
22617        &abs_path,
22618        vec![
22619            (0, Breakpoint::new_standard()),
22620            (3, Breakpoint::new_standard()),
22621        ],
22622    );
22623
22624    editor.update_in(cx, |editor, window, cx| {
22625        editor.move_to_beginning(&MoveToBeginning, window, cx);
22626        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22627    });
22628
22629    let breakpoints = editor.update(cx, |editor, cx| {
22630        editor
22631            .breakpoint_store()
22632            .as_ref()
22633            .unwrap()
22634            .read(cx)
22635            .all_source_breakpoints(cx)
22636    });
22637
22638    assert_eq!(1, breakpoints.len());
22639    assert_breakpoint(
22640        &breakpoints,
22641        &abs_path,
22642        vec![(3, Breakpoint::new_standard())],
22643    );
22644
22645    editor.update_in(cx, |editor, window, cx| {
22646        editor.move_to_end(&MoveToEnd, window, cx);
22647        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22648    });
22649
22650    let breakpoints = editor.update(cx, |editor, cx| {
22651        editor
22652            .breakpoint_store()
22653            .as_ref()
22654            .unwrap()
22655            .read(cx)
22656            .all_source_breakpoints(cx)
22657    });
22658
22659    assert_eq!(0, breakpoints.len());
22660    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22661}
22662
22663#[gpui::test]
22664async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
22665    init_test(cx, |_| {});
22666
22667    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22668
22669    let fs = FakeFs::new(cx.executor());
22670    fs.insert_tree(
22671        path!("/a"),
22672        json!({
22673            "main.rs": sample_text,
22674        }),
22675    )
22676    .await;
22677    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22678    let (workspace, cx) =
22679        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22680
22681    let worktree_id = workspace.update(cx, |workspace, cx| {
22682        workspace.project().update(cx, |project, cx| {
22683            project.worktrees(cx).next().unwrap().read(cx).id()
22684        })
22685    });
22686
22687    let buffer = project
22688        .update(cx, |project, cx| {
22689            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22690        })
22691        .await
22692        .unwrap();
22693
22694    let (editor, cx) = cx.add_window_view(|window, cx| {
22695        Editor::new(
22696            EditorMode::full(),
22697            MultiBuffer::build_from_buffer(buffer, cx),
22698            Some(project.clone()),
22699            window,
22700            cx,
22701        )
22702    });
22703
22704    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22705    let abs_path = project.read_with(cx, |project, cx| {
22706        project
22707            .absolute_path(&project_path, cx)
22708            .map(Arc::from)
22709            .unwrap()
22710    });
22711
22712    editor.update_in(cx, |editor, window, cx| {
22713        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22714    });
22715
22716    let breakpoints = editor.update(cx, |editor, cx| {
22717        editor
22718            .breakpoint_store()
22719            .as_ref()
22720            .unwrap()
22721            .read(cx)
22722            .all_source_breakpoints(cx)
22723    });
22724
22725    assert_breakpoint(
22726        &breakpoints,
22727        &abs_path,
22728        vec![(0, Breakpoint::new_log("hello world"))],
22729    );
22730
22731    // Removing a log message from a log breakpoint should remove it
22732    editor.update_in(cx, |editor, window, cx| {
22733        add_log_breakpoint_at_cursor(editor, "", window, cx);
22734    });
22735
22736    let breakpoints = editor.update(cx, |editor, cx| {
22737        editor
22738            .breakpoint_store()
22739            .as_ref()
22740            .unwrap()
22741            .read(cx)
22742            .all_source_breakpoints(cx)
22743    });
22744
22745    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22746
22747    editor.update_in(cx, |editor, window, cx| {
22748        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22749        editor.move_to_end(&MoveToEnd, window, cx);
22750        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22751        // Not adding a log message to a standard breakpoint shouldn't remove it
22752        add_log_breakpoint_at_cursor(editor, "", window, cx);
22753    });
22754
22755    let breakpoints = editor.update(cx, |editor, cx| {
22756        editor
22757            .breakpoint_store()
22758            .as_ref()
22759            .unwrap()
22760            .read(cx)
22761            .all_source_breakpoints(cx)
22762    });
22763
22764    assert_breakpoint(
22765        &breakpoints,
22766        &abs_path,
22767        vec![
22768            (0, Breakpoint::new_standard()),
22769            (3, Breakpoint::new_standard()),
22770        ],
22771    );
22772
22773    editor.update_in(cx, |editor, window, cx| {
22774        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22775    });
22776
22777    let breakpoints = editor.update(cx, |editor, cx| {
22778        editor
22779            .breakpoint_store()
22780            .as_ref()
22781            .unwrap()
22782            .read(cx)
22783            .all_source_breakpoints(cx)
22784    });
22785
22786    assert_breakpoint(
22787        &breakpoints,
22788        &abs_path,
22789        vec![
22790            (0, Breakpoint::new_standard()),
22791            (3, Breakpoint::new_log("hello world")),
22792        ],
22793    );
22794
22795    editor.update_in(cx, |editor, window, cx| {
22796        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
22797    });
22798
22799    let breakpoints = editor.update(cx, |editor, cx| {
22800        editor
22801            .breakpoint_store()
22802            .as_ref()
22803            .unwrap()
22804            .read(cx)
22805            .all_source_breakpoints(cx)
22806    });
22807
22808    assert_breakpoint(
22809        &breakpoints,
22810        &abs_path,
22811        vec![
22812            (0, Breakpoint::new_standard()),
22813            (3, Breakpoint::new_log("hello Earth!!")),
22814        ],
22815    );
22816}
22817
22818/// This also tests that Editor::breakpoint_at_cursor_head is working properly
22819/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
22820/// or when breakpoints were placed out of order. This tests for a regression too
22821#[gpui::test]
22822async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
22823    init_test(cx, |_| {});
22824
22825    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22826    let fs = FakeFs::new(cx.executor());
22827    fs.insert_tree(
22828        path!("/a"),
22829        json!({
22830            "main.rs": sample_text,
22831        }),
22832    )
22833    .await;
22834    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22835    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22836    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22837
22838    let fs = FakeFs::new(cx.executor());
22839    fs.insert_tree(
22840        path!("/a"),
22841        json!({
22842            "main.rs": sample_text,
22843        }),
22844    )
22845    .await;
22846    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22847    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22848    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22849    let worktree_id = workspace
22850        .update(cx, |workspace, _window, cx| {
22851            workspace.project().update(cx, |project, cx| {
22852                project.worktrees(cx).next().unwrap().read(cx).id()
22853            })
22854        })
22855        .unwrap();
22856
22857    let buffer = project
22858        .update(cx, |project, cx| {
22859            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22860        })
22861        .await
22862        .unwrap();
22863
22864    let (editor, cx) = cx.add_window_view(|window, cx| {
22865        Editor::new(
22866            EditorMode::full(),
22867            MultiBuffer::build_from_buffer(buffer, cx),
22868            Some(project.clone()),
22869            window,
22870            cx,
22871        )
22872    });
22873
22874    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22875    let abs_path = project.read_with(cx, |project, cx| {
22876        project
22877            .absolute_path(&project_path, cx)
22878            .map(Arc::from)
22879            .unwrap()
22880    });
22881
22882    // assert we can add breakpoint on the first line
22883    editor.update_in(cx, |editor, window, cx| {
22884        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22885        editor.move_to_end(&MoveToEnd, window, cx);
22886        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22887        editor.move_up(&MoveUp, window, cx);
22888        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22889    });
22890
22891    let breakpoints = editor.update(cx, |editor, cx| {
22892        editor
22893            .breakpoint_store()
22894            .as_ref()
22895            .unwrap()
22896            .read(cx)
22897            .all_source_breakpoints(cx)
22898    });
22899
22900    assert_eq!(1, breakpoints.len());
22901    assert_breakpoint(
22902        &breakpoints,
22903        &abs_path,
22904        vec![
22905            (0, Breakpoint::new_standard()),
22906            (2, Breakpoint::new_standard()),
22907            (3, Breakpoint::new_standard()),
22908        ],
22909    );
22910
22911    editor.update_in(cx, |editor, window, cx| {
22912        editor.move_to_beginning(&MoveToBeginning, window, cx);
22913        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22914        editor.move_to_end(&MoveToEnd, window, cx);
22915        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22916        // Disabling a breakpoint that doesn't exist should do nothing
22917        editor.move_up(&MoveUp, window, cx);
22918        editor.move_up(&MoveUp, window, cx);
22919        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22920    });
22921
22922    let breakpoints = editor.update(cx, |editor, cx| {
22923        editor
22924            .breakpoint_store()
22925            .as_ref()
22926            .unwrap()
22927            .read(cx)
22928            .all_source_breakpoints(cx)
22929    });
22930
22931    let disable_breakpoint = {
22932        let mut bp = Breakpoint::new_standard();
22933        bp.state = BreakpointState::Disabled;
22934        bp
22935    };
22936
22937    assert_eq!(1, breakpoints.len());
22938    assert_breakpoint(
22939        &breakpoints,
22940        &abs_path,
22941        vec![
22942            (0, disable_breakpoint.clone()),
22943            (2, Breakpoint::new_standard()),
22944            (3, disable_breakpoint.clone()),
22945        ],
22946    );
22947
22948    editor.update_in(cx, |editor, window, cx| {
22949        editor.move_to_beginning(&MoveToBeginning, window, cx);
22950        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22951        editor.move_to_end(&MoveToEnd, window, cx);
22952        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22953        editor.move_up(&MoveUp, window, cx);
22954        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22955    });
22956
22957    let breakpoints = editor.update(cx, |editor, cx| {
22958        editor
22959            .breakpoint_store()
22960            .as_ref()
22961            .unwrap()
22962            .read(cx)
22963            .all_source_breakpoints(cx)
22964    });
22965
22966    assert_eq!(1, breakpoints.len());
22967    assert_breakpoint(
22968        &breakpoints,
22969        &abs_path,
22970        vec![
22971            (0, Breakpoint::new_standard()),
22972            (2, disable_breakpoint),
22973            (3, Breakpoint::new_standard()),
22974        ],
22975    );
22976}
22977
22978#[gpui::test]
22979async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
22980    init_test(cx, |_| {});
22981    let capabilities = lsp::ServerCapabilities {
22982        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
22983            prepare_provider: Some(true),
22984            work_done_progress_options: Default::default(),
22985        })),
22986        ..Default::default()
22987    };
22988    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
22989
22990    cx.set_state(indoc! {"
22991        struct Fˇoo {}
22992    "});
22993
22994    cx.update_editor(|editor, _, cx| {
22995        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
22996        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
22997        editor.highlight_background::<DocumentHighlightRead>(
22998            &[highlight_range],
22999            |theme| theme.colors().editor_document_highlight_read_background,
23000            cx,
23001        );
23002    });
23003
23004    let mut prepare_rename_handler = cx
23005        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
23006            move |_, _, _| async move {
23007                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
23008                    start: lsp::Position {
23009                        line: 0,
23010                        character: 7,
23011                    },
23012                    end: lsp::Position {
23013                        line: 0,
23014                        character: 10,
23015                    },
23016                })))
23017            },
23018        );
23019    let prepare_rename_task = cx
23020        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23021        .expect("Prepare rename was not started");
23022    prepare_rename_handler.next().await.unwrap();
23023    prepare_rename_task.await.expect("Prepare rename failed");
23024
23025    let mut rename_handler =
23026        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23027            let edit = lsp::TextEdit {
23028                range: lsp::Range {
23029                    start: lsp::Position {
23030                        line: 0,
23031                        character: 7,
23032                    },
23033                    end: lsp::Position {
23034                        line: 0,
23035                        character: 10,
23036                    },
23037                },
23038                new_text: "FooRenamed".to_string(),
23039            };
23040            Ok(Some(lsp::WorkspaceEdit::new(
23041                // Specify the same edit twice
23042                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
23043            )))
23044        });
23045    let rename_task = cx
23046        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23047        .expect("Confirm rename was not started");
23048    rename_handler.next().await.unwrap();
23049    rename_task.await.expect("Confirm rename failed");
23050    cx.run_until_parked();
23051
23052    // Despite two edits, only one is actually applied as those are identical
23053    cx.assert_editor_state(indoc! {"
23054        struct FooRenamedˇ {}
23055    "});
23056}
23057
23058#[gpui::test]
23059async fn test_rename_without_prepare(cx: &mut TestAppContext) {
23060    init_test(cx, |_| {});
23061    // These capabilities indicate that the server does not support prepare rename.
23062    let capabilities = lsp::ServerCapabilities {
23063        rename_provider: Some(lsp::OneOf::Left(true)),
23064        ..Default::default()
23065    };
23066    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23067
23068    cx.set_state(indoc! {"
23069        struct Fˇoo {}
23070    "});
23071
23072    cx.update_editor(|editor, _window, cx| {
23073        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23074        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23075        editor.highlight_background::<DocumentHighlightRead>(
23076            &[highlight_range],
23077            |theme| theme.colors().editor_document_highlight_read_background,
23078            cx,
23079        );
23080    });
23081
23082    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23083        .expect("Prepare rename was not started")
23084        .await
23085        .expect("Prepare rename failed");
23086
23087    let mut rename_handler =
23088        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23089            let edit = lsp::TextEdit {
23090                range: lsp::Range {
23091                    start: lsp::Position {
23092                        line: 0,
23093                        character: 7,
23094                    },
23095                    end: lsp::Position {
23096                        line: 0,
23097                        character: 10,
23098                    },
23099                },
23100                new_text: "FooRenamed".to_string(),
23101            };
23102            Ok(Some(lsp::WorkspaceEdit::new(
23103                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23104            )))
23105        });
23106    let rename_task = cx
23107        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23108        .expect("Confirm rename was not started");
23109    rename_handler.next().await.unwrap();
23110    rename_task.await.expect("Confirm rename failed");
23111    cx.run_until_parked();
23112
23113    // Correct range is renamed, as `surrounding_word` is used to find it.
23114    cx.assert_editor_state(indoc! {"
23115        struct FooRenamedˇ {}
23116    "});
23117}
23118
23119#[gpui::test]
23120async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23121    init_test(cx, |_| {});
23122    let mut cx = EditorTestContext::new(cx).await;
23123
23124    let language = Arc::new(
23125        Language::new(
23126            LanguageConfig::default(),
23127            Some(tree_sitter_html::LANGUAGE.into()),
23128        )
23129        .with_brackets_query(
23130            r#"
23131            ("<" @open "/>" @close)
23132            ("</" @open ">" @close)
23133            ("<" @open ">" @close)
23134            ("\"" @open "\"" @close)
23135            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23136        "#,
23137        )
23138        .unwrap(),
23139    );
23140    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23141
23142    cx.set_state(indoc! {"
23143        <span>ˇ</span>
23144    "});
23145    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23146    cx.assert_editor_state(indoc! {"
23147        <span>
23148        ˇ
23149        </span>
23150    "});
23151
23152    cx.set_state(indoc! {"
23153        <span><span></span>ˇ</span>
23154    "});
23155    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23156    cx.assert_editor_state(indoc! {"
23157        <span><span></span>
23158        ˇ</span>
23159    "});
23160
23161    cx.set_state(indoc! {"
23162        <span>ˇ
23163        </span>
23164    "});
23165    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23166    cx.assert_editor_state(indoc! {"
23167        <span>
23168        ˇ
23169        </span>
23170    "});
23171}
23172
23173#[gpui::test(iterations = 10)]
23174async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23175    init_test(cx, |_| {});
23176
23177    let fs = FakeFs::new(cx.executor());
23178    fs.insert_tree(
23179        path!("/dir"),
23180        json!({
23181            "a.ts": "a",
23182        }),
23183    )
23184    .await;
23185
23186    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23187    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23188    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23189
23190    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23191    language_registry.add(Arc::new(Language::new(
23192        LanguageConfig {
23193            name: "TypeScript".into(),
23194            matcher: LanguageMatcher {
23195                path_suffixes: vec!["ts".to_string()],
23196                ..Default::default()
23197            },
23198            ..Default::default()
23199        },
23200        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
23201    )));
23202    let mut fake_language_servers = language_registry.register_fake_lsp(
23203        "TypeScript",
23204        FakeLspAdapter {
23205            capabilities: lsp::ServerCapabilities {
23206                code_lens_provider: Some(lsp::CodeLensOptions {
23207                    resolve_provider: Some(true),
23208                }),
23209                execute_command_provider: Some(lsp::ExecuteCommandOptions {
23210                    commands: vec!["_the/command".to_string()],
23211                    ..lsp::ExecuteCommandOptions::default()
23212                }),
23213                ..lsp::ServerCapabilities::default()
23214            },
23215            ..FakeLspAdapter::default()
23216        },
23217    );
23218
23219    let editor = workspace
23220        .update(cx, |workspace, window, cx| {
23221            workspace.open_abs_path(
23222                PathBuf::from(path!("/dir/a.ts")),
23223                OpenOptions::default(),
23224                window,
23225                cx,
23226            )
23227        })
23228        .unwrap()
23229        .await
23230        .unwrap()
23231        .downcast::<Editor>()
23232        .unwrap();
23233    cx.executor().run_until_parked();
23234
23235    let fake_server = fake_language_servers.next().await.unwrap();
23236
23237    let buffer = editor.update(cx, |editor, cx| {
23238        editor
23239            .buffer()
23240            .read(cx)
23241            .as_singleton()
23242            .expect("have opened a single file by path")
23243    });
23244
23245    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
23246    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
23247    drop(buffer_snapshot);
23248    let actions = cx
23249        .update_window(*workspace, |_, window, cx| {
23250            project.code_actions(&buffer, anchor..anchor, window, cx)
23251        })
23252        .unwrap();
23253
23254    fake_server
23255        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23256            Ok(Some(vec![
23257                lsp::CodeLens {
23258                    range: lsp::Range::default(),
23259                    command: Some(lsp::Command {
23260                        title: "Code lens command".to_owned(),
23261                        command: "_the/command".to_owned(),
23262                        arguments: None,
23263                    }),
23264                    data: None,
23265                },
23266                lsp::CodeLens {
23267                    range: lsp::Range::default(),
23268                    command: Some(lsp::Command {
23269                        title: "Command not in capabilities".to_owned(),
23270                        command: "not in capabilities".to_owned(),
23271                        arguments: None,
23272                    }),
23273                    data: None,
23274                },
23275                lsp::CodeLens {
23276                    range: lsp::Range {
23277                        start: lsp::Position {
23278                            line: 1,
23279                            character: 1,
23280                        },
23281                        end: lsp::Position {
23282                            line: 1,
23283                            character: 1,
23284                        },
23285                    },
23286                    command: Some(lsp::Command {
23287                        title: "Command not in range".to_owned(),
23288                        command: "_the/command".to_owned(),
23289                        arguments: None,
23290                    }),
23291                    data: None,
23292                },
23293            ]))
23294        })
23295        .next()
23296        .await;
23297
23298    let actions = actions.await.unwrap();
23299    assert_eq!(
23300        actions.len(),
23301        1,
23302        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
23303    );
23304    let action = actions[0].clone();
23305    let apply = project.update(cx, |project, cx| {
23306        project.apply_code_action(buffer.clone(), action, true, cx)
23307    });
23308
23309    // Resolving the code action does not populate its edits. In absence of
23310    // edits, we must execute the given command.
23311    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
23312        |mut lens, _| async move {
23313            let lens_command = lens.command.as_mut().expect("should have a command");
23314            assert_eq!(lens_command.title, "Code lens command");
23315            lens_command.arguments = Some(vec![json!("the-argument")]);
23316            Ok(lens)
23317        },
23318    );
23319
23320    // While executing the command, the language server sends the editor
23321    // a `workspaceEdit` request.
23322    fake_server
23323        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
23324            let fake = fake_server.clone();
23325            move |params, _| {
23326                assert_eq!(params.command, "_the/command");
23327                let fake = fake.clone();
23328                async move {
23329                    fake.server
23330                        .request::<lsp::request::ApplyWorkspaceEdit>(
23331                            lsp::ApplyWorkspaceEditParams {
23332                                label: None,
23333                                edit: lsp::WorkspaceEdit {
23334                                    changes: Some(
23335                                        [(
23336                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
23337                                            vec![lsp::TextEdit {
23338                                                range: lsp::Range::new(
23339                                                    lsp::Position::new(0, 0),
23340                                                    lsp::Position::new(0, 0),
23341                                                ),
23342                                                new_text: "X".into(),
23343                                            }],
23344                                        )]
23345                                        .into_iter()
23346                                        .collect(),
23347                                    ),
23348                                    ..lsp::WorkspaceEdit::default()
23349                                },
23350                            },
23351                        )
23352                        .await
23353                        .into_response()
23354                        .unwrap();
23355                    Ok(Some(json!(null)))
23356                }
23357            }
23358        })
23359        .next()
23360        .await;
23361
23362    // Applying the code lens command returns a project transaction containing the edits
23363    // sent by the language server in its `workspaceEdit` request.
23364    let transaction = apply.await.unwrap();
23365    assert!(transaction.0.contains_key(&buffer));
23366    buffer.update(cx, |buffer, cx| {
23367        assert_eq!(buffer.text(), "Xa");
23368        buffer.undo(cx);
23369        assert_eq!(buffer.text(), "a");
23370    });
23371
23372    let actions_after_edits = cx
23373        .update_window(*workspace, |_, window, cx| {
23374            project.code_actions(&buffer, anchor..anchor, window, cx)
23375        })
23376        .unwrap()
23377        .await
23378        .unwrap();
23379    assert_eq!(
23380        actions, actions_after_edits,
23381        "For the same selection, same code lens actions should be returned"
23382    );
23383
23384    let _responses =
23385        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23386            panic!("No more code lens requests are expected");
23387        });
23388    editor.update_in(cx, |editor, window, cx| {
23389        editor.select_all(&SelectAll, window, cx);
23390    });
23391    cx.executor().run_until_parked();
23392    let new_actions = cx
23393        .update_window(*workspace, |_, window, cx| {
23394            project.code_actions(&buffer, anchor..anchor, window, cx)
23395        })
23396        .unwrap()
23397        .await
23398        .unwrap();
23399    assert_eq!(
23400        actions, new_actions,
23401        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
23402    );
23403}
23404
23405#[gpui::test]
23406async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
23407    init_test(cx, |_| {});
23408
23409    let fs = FakeFs::new(cx.executor());
23410    let main_text = r#"fn main() {
23411println!("1");
23412println!("2");
23413println!("3");
23414println!("4");
23415println!("5");
23416}"#;
23417    let lib_text = "mod foo {}";
23418    fs.insert_tree(
23419        path!("/a"),
23420        json!({
23421            "lib.rs": lib_text,
23422            "main.rs": main_text,
23423        }),
23424    )
23425    .await;
23426
23427    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23428    let (workspace, cx) =
23429        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23430    let worktree_id = workspace.update(cx, |workspace, cx| {
23431        workspace.project().update(cx, |project, cx| {
23432            project.worktrees(cx).next().unwrap().read(cx).id()
23433        })
23434    });
23435
23436    let expected_ranges = vec![
23437        Point::new(0, 0)..Point::new(0, 0),
23438        Point::new(1, 0)..Point::new(1, 1),
23439        Point::new(2, 0)..Point::new(2, 2),
23440        Point::new(3, 0)..Point::new(3, 3),
23441    ];
23442
23443    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23444    let editor_1 = workspace
23445        .update_in(cx, |workspace, window, cx| {
23446            workspace.open_path(
23447                (worktree_id, rel_path("main.rs")),
23448                Some(pane_1.downgrade()),
23449                true,
23450                window,
23451                cx,
23452            )
23453        })
23454        .unwrap()
23455        .await
23456        .downcast::<Editor>()
23457        .unwrap();
23458    pane_1.update(cx, |pane, cx| {
23459        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23460        open_editor.update(cx, |editor, cx| {
23461            assert_eq!(
23462                editor.display_text(cx),
23463                main_text,
23464                "Original main.rs text on initial open",
23465            );
23466            assert_eq!(
23467                editor
23468                    .selections
23469                    .all::<Point>(cx)
23470                    .into_iter()
23471                    .map(|s| s.range())
23472                    .collect::<Vec<_>>(),
23473                vec![Point::zero()..Point::zero()],
23474                "Default selections on initial open",
23475            );
23476        })
23477    });
23478    editor_1.update_in(cx, |editor, window, cx| {
23479        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23480            s.select_ranges(expected_ranges.clone());
23481        });
23482    });
23483
23484    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
23485        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
23486    });
23487    let editor_2 = workspace
23488        .update_in(cx, |workspace, window, cx| {
23489            workspace.open_path(
23490                (worktree_id, rel_path("main.rs")),
23491                Some(pane_2.downgrade()),
23492                true,
23493                window,
23494                cx,
23495            )
23496        })
23497        .unwrap()
23498        .await
23499        .downcast::<Editor>()
23500        .unwrap();
23501    pane_2.update(cx, |pane, cx| {
23502        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23503        open_editor.update(cx, |editor, cx| {
23504            assert_eq!(
23505                editor.display_text(cx),
23506                main_text,
23507                "Original main.rs text on initial open in another panel",
23508            );
23509            assert_eq!(
23510                editor
23511                    .selections
23512                    .all::<Point>(cx)
23513                    .into_iter()
23514                    .map(|s| s.range())
23515                    .collect::<Vec<_>>(),
23516                vec![Point::zero()..Point::zero()],
23517                "Default selections on initial open in another panel",
23518            );
23519        })
23520    });
23521
23522    editor_2.update_in(cx, |editor, window, cx| {
23523        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23524    });
23525
23526    let _other_editor_1 = workspace
23527        .update_in(cx, |workspace, window, cx| {
23528            workspace.open_path(
23529                (worktree_id, rel_path("lib.rs")),
23530                Some(pane_1.downgrade()),
23531                true,
23532                window,
23533                cx,
23534            )
23535        })
23536        .unwrap()
23537        .await
23538        .downcast::<Editor>()
23539        .unwrap();
23540    pane_1
23541        .update_in(cx, |pane, window, cx| {
23542            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23543        })
23544        .await
23545        .unwrap();
23546    drop(editor_1);
23547    pane_1.update(cx, |pane, cx| {
23548        pane.active_item()
23549            .unwrap()
23550            .downcast::<Editor>()
23551            .unwrap()
23552            .update(cx, |editor, cx| {
23553                assert_eq!(
23554                    editor.display_text(cx),
23555                    lib_text,
23556                    "Other file should be open and active",
23557                );
23558            });
23559        assert_eq!(pane.items().count(), 1, "No other editors should be open");
23560    });
23561
23562    let _other_editor_2 = workspace
23563        .update_in(cx, |workspace, window, cx| {
23564            workspace.open_path(
23565                (worktree_id, rel_path("lib.rs")),
23566                Some(pane_2.downgrade()),
23567                true,
23568                window,
23569                cx,
23570            )
23571        })
23572        .unwrap()
23573        .await
23574        .downcast::<Editor>()
23575        .unwrap();
23576    pane_2
23577        .update_in(cx, |pane, window, cx| {
23578            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23579        })
23580        .await
23581        .unwrap();
23582    drop(editor_2);
23583    pane_2.update(cx, |pane, cx| {
23584        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23585        open_editor.update(cx, |editor, cx| {
23586            assert_eq!(
23587                editor.display_text(cx),
23588                lib_text,
23589                "Other file should be open and active in another panel too",
23590            );
23591        });
23592        assert_eq!(
23593            pane.items().count(),
23594            1,
23595            "No other editors should be open in another pane",
23596        );
23597    });
23598
23599    let _editor_1_reopened = workspace
23600        .update_in(cx, |workspace, window, cx| {
23601            workspace.open_path(
23602                (worktree_id, rel_path("main.rs")),
23603                Some(pane_1.downgrade()),
23604                true,
23605                window,
23606                cx,
23607            )
23608        })
23609        .unwrap()
23610        .await
23611        .downcast::<Editor>()
23612        .unwrap();
23613    let _editor_2_reopened = workspace
23614        .update_in(cx, |workspace, window, cx| {
23615            workspace.open_path(
23616                (worktree_id, rel_path("main.rs")),
23617                Some(pane_2.downgrade()),
23618                true,
23619                window,
23620                cx,
23621            )
23622        })
23623        .unwrap()
23624        .await
23625        .downcast::<Editor>()
23626        .unwrap();
23627    pane_1.update(cx, |pane, cx| {
23628        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23629        open_editor.update(cx, |editor, cx| {
23630            assert_eq!(
23631                editor.display_text(cx),
23632                main_text,
23633                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
23634            );
23635            assert_eq!(
23636                editor
23637                    .selections
23638                    .all::<Point>(cx)
23639                    .into_iter()
23640                    .map(|s| s.range())
23641                    .collect::<Vec<_>>(),
23642                expected_ranges,
23643                "Previous editor in the 1st panel had selections and should get them restored on reopen",
23644            );
23645        })
23646    });
23647    pane_2.update(cx, |pane, cx| {
23648        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23649        open_editor.update(cx, |editor, cx| {
23650            assert_eq!(
23651                editor.display_text(cx),
23652                r#"fn main() {
23653⋯rintln!("1");
23654⋯intln!("2");
23655⋯ntln!("3");
23656println!("4");
23657println!("5");
23658}"#,
23659                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
23660            );
23661            assert_eq!(
23662                editor
23663                    .selections
23664                    .all::<Point>(cx)
23665                    .into_iter()
23666                    .map(|s| s.range())
23667                    .collect::<Vec<_>>(),
23668                vec![Point::zero()..Point::zero()],
23669                "Previous editor in the 2nd pane had no selections changed hence should restore none",
23670            );
23671        })
23672    });
23673}
23674
23675#[gpui::test]
23676async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
23677    init_test(cx, |_| {});
23678
23679    let fs = FakeFs::new(cx.executor());
23680    let main_text = r#"fn main() {
23681println!("1");
23682println!("2");
23683println!("3");
23684println!("4");
23685println!("5");
23686}"#;
23687    let lib_text = "mod foo {}";
23688    fs.insert_tree(
23689        path!("/a"),
23690        json!({
23691            "lib.rs": lib_text,
23692            "main.rs": main_text,
23693        }),
23694    )
23695    .await;
23696
23697    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23698    let (workspace, cx) =
23699        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23700    let worktree_id = workspace.update(cx, |workspace, cx| {
23701        workspace.project().update(cx, |project, cx| {
23702            project.worktrees(cx).next().unwrap().read(cx).id()
23703        })
23704    });
23705
23706    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23707    let editor = workspace
23708        .update_in(cx, |workspace, window, cx| {
23709            workspace.open_path(
23710                (worktree_id, rel_path("main.rs")),
23711                Some(pane.downgrade()),
23712                true,
23713                window,
23714                cx,
23715            )
23716        })
23717        .unwrap()
23718        .await
23719        .downcast::<Editor>()
23720        .unwrap();
23721    pane.update(cx, |pane, cx| {
23722        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23723        open_editor.update(cx, |editor, cx| {
23724            assert_eq!(
23725                editor.display_text(cx),
23726                main_text,
23727                "Original main.rs text on initial open",
23728            );
23729        })
23730    });
23731    editor.update_in(cx, |editor, window, cx| {
23732        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
23733    });
23734
23735    cx.update_global(|store: &mut SettingsStore, cx| {
23736        store.update_user_settings(cx, |s| {
23737            s.workspace.restore_on_file_reopen = Some(false);
23738        });
23739    });
23740    editor.update_in(cx, |editor, window, cx| {
23741        editor.fold_ranges(
23742            vec![
23743                Point::new(1, 0)..Point::new(1, 1),
23744                Point::new(2, 0)..Point::new(2, 2),
23745                Point::new(3, 0)..Point::new(3, 3),
23746            ],
23747            false,
23748            window,
23749            cx,
23750        );
23751    });
23752    pane.update_in(cx, |pane, window, cx| {
23753        pane.close_all_items(&CloseAllItems::default(), window, cx)
23754    })
23755    .await
23756    .unwrap();
23757    pane.update(cx, |pane, _| {
23758        assert!(pane.active_item().is_none());
23759    });
23760    cx.update_global(|store: &mut SettingsStore, cx| {
23761        store.update_user_settings(cx, |s| {
23762            s.workspace.restore_on_file_reopen = Some(true);
23763        });
23764    });
23765
23766    let _editor_reopened = workspace
23767        .update_in(cx, |workspace, window, cx| {
23768            workspace.open_path(
23769                (worktree_id, rel_path("main.rs")),
23770                Some(pane.downgrade()),
23771                true,
23772                window,
23773                cx,
23774            )
23775        })
23776        .unwrap()
23777        .await
23778        .downcast::<Editor>()
23779        .unwrap();
23780    pane.update(cx, |pane, cx| {
23781        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23782        open_editor.update(cx, |editor, cx| {
23783            assert_eq!(
23784                editor.display_text(cx),
23785                main_text,
23786                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
23787            );
23788        })
23789    });
23790}
23791
23792#[gpui::test]
23793async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
23794    struct EmptyModalView {
23795        focus_handle: gpui::FocusHandle,
23796    }
23797    impl EventEmitter<DismissEvent> for EmptyModalView {}
23798    impl Render for EmptyModalView {
23799        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
23800            div()
23801        }
23802    }
23803    impl Focusable for EmptyModalView {
23804        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
23805            self.focus_handle.clone()
23806        }
23807    }
23808    impl workspace::ModalView for EmptyModalView {}
23809    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
23810        EmptyModalView {
23811            focus_handle: cx.focus_handle(),
23812        }
23813    }
23814
23815    init_test(cx, |_| {});
23816
23817    let fs = FakeFs::new(cx.executor());
23818    let project = Project::test(fs, [], cx).await;
23819    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23820    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
23821    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23822    let editor = cx.new_window_entity(|window, cx| {
23823        Editor::new(
23824            EditorMode::full(),
23825            buffer,
23826            Some(project.clone()),
23827            window,
23828            cx,
23829        )
23830    });
23831    workspace
23832        .update(cx, |workspace, window, cx| {
23833            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
23834        })
23835        .unwrap();
23836    editor.update_in(cx, |editor, window, cx| {
23837        editor.open_context_menu(&OpenContextMenu, window, cx);
23838        assert!(editor.mouse_context_menu.is_some());
23839    });
23840    workspace
23841        .update(cx, |workspace, window, cx| {
23842            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
23843        })
23844        .unwrap();
23845    cx.read(|cx| {
23846        assert!(editor.read(cx).mouse_context_menu.is_none());
23847    });
23848}
23849
23850fn set_linked_edit_ranges(
23851    opening: (Point, Point),
23852    closing: (Point, Point),
23853    editor: &mut Editor,
23854    cx: &mut Context<Editor>,
23855) {
23856    let Some((buffer, _)) = editor
23857        .buffer
23858        .read(cx)
23859        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
23860    else {
23861        panic!("Failed to get buffer for selection position");
23862    };
23863    let buffer = buffer.read(cx);
23864    let buffer_id = buffer.remote_id();
23865    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
23866    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
23867    let mut linked_ranges = HashMap::default();
23868    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
23869    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
23870}
23871
23872#[gpui::test]
23873async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
23874    init_test(cx, |_| {});
23875
23876    let fs = FakeFs::new(cx.executor());
23877    fs.insert_file(path!("/file.html"), Default::default())
23878        .await;
23879
23880    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
23881
23882    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23883    let html_language = Arc::new(Language::new(
23884        LanguageConfig {
23885            name: "HTML".into(),
23886            matcher: LanguageMatcher {
23887                path_suffixes: vec!["html".to_string()],
23888                ..LanguageMatcher::default()
23889            },
23890            brackets: BracketPairConfig {
23891                pairs: vec![BracketPair {
23892                    start: "<".into(),
23893                    end: ">".into(),
23894                    close: true,
23895                    ..Default::default()
23896                }],
23897                ..Default::default()
23898            },
23899            ..Default::default()
23900        },
23901        Some(tree_sitter_html::LANGUAGE.into()),
23902    ));
23903    language_registry.add(html_language);
23904    let mut fake_servers = language_registry.register_fake_lsp(
23905        "HTML",
23906        FakeLspAdapter {
23907            capabilities: lsp::ServerCapabilities {
23908                completion_provider: Some(lsp::CompletionOptions {
23909                    resolve_provider: Some(true),
23910                    ..Default::default()
23911                }),
23912                ..Default::default()
23913            },
23914            ..Default::default()
23915        },
23916    );
23917
23918    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23919    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23920
23921    let worktree_id = workspace
23922        .update(cx, |workspace, _window, cx| {
23923            workspace.project().update(cx, |project, cx| {
23924                project.worktrees(cx).next().unwrap().read(cx).id()
23925            })
23926        })
23927        .unwrap();
23928    project
23929        .update(cx, |project, cx| {
23930            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
23931        })
23932        .await
23933        .unwrap();
23934    let editor = workspace
23935        .update(cx, |workspace, window, cx| {
23936            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
23937        })
23938        .unwrap()
23939        .await
23940        .unwrap()
23941        .downcast::<Editor>()
23942        .unwrap();
23943
23944    let fake_server = fake_servers.next().await.unwrap();
23945    editor.update_in(cx, |editor, window, cx| {
23946        editor.set_text("<ad></ad>", window, cx);
23947        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23948            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
23949        });
23950        set_linked_edit_ranges(
23951            (Point::new(0, 1), Point::new(0, 3)),
23952            (Point::new(0, 6), Point::new(0, 8)),
23953            editor,
23954            cx,
23955        );
23956    });
23957    let mut completion_handle =
23958        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
23959            Ok(Some(lsp::CompletionResponse::Array(vec![
23960                lsp::CompletionItem {
23961                    label: "head".to_string(),
23962                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23963                        lsp::InsertReplaceEdit {
23964                            new_text: "head".to_string(),
23965                            insert: lsp::Range::new(
23966                                lsp::Position::new(0, 1),
23967                                lsp::Position::new(0, 3),
23968                            ),
23969                            replace: lsp::Range::new(
23970                                lsp::Position::new(0, 1),
23971                                lsp::Position::new(0, 3),
23972                            ),
23973                        },
23974                    )),
23975                    ..Default::default()
23976                },
23977            ])))
23978        });
23979    editor.update_in(cx, |editor, window, cx| {
23980        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
23981    });
23982    cx.run_until_parked();
23983    completion_handle.next().await.unwrap();
23984    editor.update(cx, |editor, _| {
23985        assert!(
23986            editor.context_menu_visible(),
23987            "Completion menu should be visible"
23988        );
23989    });
23990    editor.update_in(cx, |editor, window, cx| {
23991        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
23992    });
23993    cx.executor().run_until_parked();
23994    editor.update(cx, |editor, cx| {
23995        assert_eq!(editor.text(cx), "<head></head>");
23996    });
23997}
23998
23999#[gpui::test]
24000async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
24001    init_test(cx, |_| {});
24002
24003    let mut cx = EditorTestContext::new(cx).await;
24004    let language = Arc::new(Language::new(
24005        LanguageConfig {
24006            name: "TSX".into(),
24007            matcher: LanguageMatcher {
24008                path_suffixes: vec!["tsx".to_string()],
24009                ..LanguageMatcher::default()
24010            },
24011            brackets: BracketPairConfig {
24012                pairs: vec![BracketPair {
24013                    start: "<".into(),
24014                    end: ">".into(),
24015                    close: true,
24016                    ..Default::default()
24017                }],
24018                ..Default::default()
24019            },
24020            linked_edit_characters: HashSet::from_iter(['.']),
24021            ..Default::default()
24022        },
24023        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
24024    ));
24025    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24026
24027    // Test typing > does not extend linked pair
24028    cx.set_state("<divˇ<div></div>");
24029    cx.update_editor(|editor, _, cx| {
24030        set_linked_edit_ranges(
24031            (Point::new(0, 1), Point::new(0, 4)),
24032            (Point::new(0, 11), Point::new(0, 14)),
24033            editor,
24034            cx,
24035        );
24036    });
24037    cx.update_editor(|editor, window, cx| {
24038        editor.handle_input(">", window, cx);
24039    });
24040    cx.assert_editor_state("<div>ˇ<div></div>");
24041
24042    // Test typing . do extend linked pair
24043    cx.set_state("<Animatedˇ></Animated>");
24044    cx.update_editor(|editor, _, cx| {
24045        set_linked_edit_ranges(
24046            (Point::new(0, 1), Point::new(0, 9)),
24047            (Point::new(0, 12), Point::new(0, 20)),
24048            editor,
24049            cx,
24050        );
24051    });
24052    cx.update_editor(|editor, window, cx| {
24053        editor.handle_input(".", window, cx);
24054    });
24055    cx.assert_editor_state("<Animated.ˇ></Animated.>");
24056    cx.update_editor(|editor, _, cx| {
24057        set_linked_edit_ranges(
24058            (Point::new(0, 1), Point::new(0, 10)),
24059            (Point::new(0, 13), Point::new(0, 21)),
24060            editor,
24061            cx,
24062        );
24063    });
24064    cx.update_editor(|editor, window, cx| {
24065        editor.handle_input("V", window, cx);
24066    });
24067    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
24068}
24069
24070#[gpui::test]
24071async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
24072    init_test(cx, |_| {});
24073
24074    let fs = FakeFs::new(cx.executor());
24075    fs.insert_tree(
24076        path!("/root"),
24077        json!({
24078            "a": {
24079                "main.rs": "fn main() {}",
24080            },
24081            "foo": {
24082                "bar": {
24083                    "external_file.rs": "pub mod external {}",
24084                }
24085            }
24086        }),
24087    )
24088    .await;
24089
24090    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
24091    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24092    language_registry.add(rust_lang());
24093    let _fake_servers = language_registry.register_fake_lsp(
24094        "Rust",
24095        FakeLspAdapter {
24096            ..FakeLspAdapter::default()
24097        },
24098    );
24099    let (workspace, cx) =
24100        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24101    let worktree_id = workspace.update(cx, |workspace, cx| {
24102        workspace.project().update(cx, |project, cx| {
24103            project.worktrees(cx).next().unwrap().read(cx).id()
24104        })
24105    });
24106
24107    let assert_language_servers_count =
24108        |expected: usize, context: &str, cx: &mut VisualTestContext| {
24109            project.update(cx, |project, cx| {
24110                let current = project
24111                    .lsp_store()
24112                    .read(cx)
24113                    .as_local()
24114                    .unwrap()
24115                    .language_servers
24116                    .len();
24117                assert_eq!(expected, current, "{context}");
24118            });
24119        };
24120
24121    assert_language_servers_count(
24122        0,
24123        "No servers should be running before any file is open",
24124        cx,
24125    );
24126    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24127    let main_editor = workspace
24128        .update_in(cx, |workspace, window, cx| {
24129            workspace.open_path(
24130                (worktree_id, rel_path("main.rs")),
24131                Some(pane.downgrade()),
24132                true,
24133                window,
24134                cx,
24135            )
24136        })
24137        .unwrap()
24138        .await
24139        .downcast::<Editor>()
24140        .unwrap();
24141    pane.update(cx, |pane, cx| {
24142        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24143        open_editor.update(cx, |editor, cx| {
24144            assert_eq!(
24145                editor.display_text(cx),
24146                "fn main() {}",
24147                "Original main.rs text on initial open",
24148            );
24149        });
24150        assert_eq!(open_editor, main_editor);
24151    });
24152    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24153
24154    let external_editor = workspace
24155        .update_in(cx, |workspace, window, cx| {
24156            workspace.open_abs_path(
24157                PathBuf::from("/root/foo/bar/external_file.rs"),
24158                OpenOptions::default(),
24159                window,
24160                cx,
24161            )
24162        })
24163        .await
24164        .expect("opening external file")
24165        .downcast::<Editor>()
24166        .expect("downcasted external file's open element to editor");
24167    pane.update(cx, |pane, cx| {
24168        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24169        open_editor.update(cx, |editor, cx| {
24170            assert_eq!(
24171                editor.display_text(cx),
24172                "pub mod external {}",
24173                "External file is open now",
24174            );
24175        });
24176        assert_eq!(open_editor, external_editor);
24177    });
24178    assert_language_servers_count(
24179        1,
24180        "Second, external, *.rs file should join the existing server",
24181        cx,
24182    );
24183
24184    pane.update_in(cx, |pane, window, cx| {
24185        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24186    })
24187    .await
24188    .unwrap();
24189    pane.update_in(cx, |pane, window, cx| {
24190        pane.navigate_backward(&Default::default(), window, cx);
24191    });
24192    cx.run_until_parked();
24193    pane.update(cx, |pane, cx| {
24194        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24195        open_editor.update(cx, |editor, cx| {
24196            assert_eq!(
24197                editor.display_text(cx),
24198                "pub mod external {}",
24199                "External file is open now",
24200            );
24201        });
24202    });
24203    assert_language_servers_count(
24204        1,
24205        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
24206        cx,
24207    );
24208
24209    cx.update(|_, cx| {
24210        workspace::reload(cx);
24211    });
24212    assert_language_servers_count(
24213        1,
24214        "After reloading the worktree with local and external files opened, only one project should be started",
24215        cx,
24216    );
24217}
24218
24219#[gpui::test]
24220async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
24221    init_test(cx, |_| {});
24222
24223    let mut cx = EditorTestContext::new(cx).await;
24224    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24225    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24226
24227    // test cursor move to start of each line on tab
24228    // for `if`, `elif`, `else`, `while`, `with` and `for`
24229    cx.set_state(indoc! {"
24230        def main():
24231        ˇ    for item in items:
24232        ˇ        while item.active:
24233        ˇ            if item.value > 10:
24234        ˇ                continue
24235        ˇ            elif item.value < 0:
24236        ˇ                break
24237        ˇ            else:
24238        ˇ                with item.context() as ctx:
24239        ˇ                    yield count
24240        ˇ        else:
24241        ˇ            log('while else')
24242        ˇ    else:
24243        ˇ        log('for else')
24244    "});
24245    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24246    cx.assert_editor_state(indoc! {"
24247        def main():
24248            ˇfor item in items:
24249                ˇwhile item.active:
24250                    ˇif item.value > 10:
24251                        ˇcontinue
24252                    ˇelif item.value < 0:
24253                        ˇbreak
24254                    ˇelse:
24255                        ˇwith item.context() as ctx:
24256                            ˇyield count
24257                ˇelse:
24258                    ˇlog('while else')
24259            ˇelse:
24260                ˇlog('for else')
24261    "});
24262    // test relative indent is preserved when tab
24263    // for `if`, `elif`, `else`, `while`, `with` and `for`
24264    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24265    cx.assert_editor_state(indoc! {"
24266        def main():
24267                ˇfor item in items:
24268                    ˇwhile item.active:
24269                        ˇif item.value > 10:
24270                            ˇcontinue
24271                        ˇelif item.value < 0:
24272                            ˇbreak
24273                        ˇelse:
24274                            ˇwith item.context() as ctx:
24275                                ˇyield count
24276                    ˇelse:
24277                        ˇlog('while else')
24278                ˇelse:
24279                    ˇlog('for else')
24280    "});
24281
24282    // test cursor move to start of each line on tab
24283    // for `try`, `except`, `else`, `finally`, `match` and `def`
24284    cx.set_state(indoc! {"
24285        def main():
24286        ˇ    try:
24287        ˇ        fetch()
24288        ˇ    except ValueError:
24289        ˇ        handle_error()
24290        ˇ    else:
24291        ˇ        match value:
24292        ˇ            case _:
24293        ˇ    finally:
24294        ˇ        def status():
24295        ˇ            return 0
24296    "});
24297    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24298    cx.assert_editor_state(indoc! {"
24299        def main():
24300            ˇtry:
24301                ˇfetch()
24302            ˇexcept ValueError:
24303                ˇhandle_error()
24304            ˇelse:
24305                ˇmatch value:
24306                    ˇcase _:
24307            ˇfinally:
24308                ˇdef status():
24309                    ˇreturn 0
24310    "});
24311    // test relative indent is preserved when tab
24312    // for `try`, `except`, `else`, `finally`, `match` and `def`
24313    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24314    cx.assert_editor_state(indoc! {"
24315        def main():
24316                ˇtry:
24317                    ˇfetch()
24318                ˇexcept ValueError:
24319                    ˇhandle_error()
24320                ˇelse:
24321                    ˇmatch value:
24322                        ˇcase _:
24323                ˇfinally:
24324                    ˇdef status():
24325                        ˇreturn 0
24326    "});
24327}
24328
24329#[gpui::test]
24330async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
24331    init_test(cx, |_| {});
24332
24333    let mut cx = EditorTestContext::new(cx).await;
24334    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24335    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24336
24337    // test `else` auto outdents when typed inside `if` block
24338    cx.set_state(indoc! {"
24339        def main():
24340            if i == 2:
24341                return
24342                ˇ
24343    "});
24344    cx.update_editor(|editor, window, cx| {
24345        editor.handle_input("else:", window, cx);
24346    });
24347    cx.assert_editor_state(indoc! {"
24348        def main():
24349            if i == 2:
24350                return
24351            else:ˇ
24352    "});
24353
24354    // test `except` auto outdents when typed inside `try` block
24355    cx.set_state(indoc! {"
24356        def main():
24357            try:
24358                i = 2
24359                ˇ
24360    "});
24361    cx.update_editor(|editor, window, cx| {
24362        editor.handle_input("except:", window, cx);
24363    });
24364    cx.assert_editor_state(indoc! {"
24365        def main():
24366            try:
24367                i = 2
24368            except:ˇ
24369    "});
24370
24371    // test `else` auto outdents when typed inside `except` block
24372    cx.set_state(indoc! {"
24373        def main():
24374            try:
24375                i = 2
24376            except:
24377                j = 2
24378                ˇ
24379    "});
24380    cx.update_editor(|editor, window, cx| {
24381        editor.handle_input("else:", window, cx);
24382    });
24383    cx.assert_editor_state(indoc! {"
24384        def main():
24385            try:
24386                i = 2
24387            except:
24388                j = 2
24389            else:ˇ
24390    "});
24391
24392    // test `finally` auto outdents when typed inside `else` block
24393    cx.set_state(indoc! {"
24394        def main():
24395            try:
24396                i = 2
24397            except:
24398                j = 2
24399            else:
24400                k = 2
24401                ˇ
24402    "});
24403    cx.update_editor(|editor, window, cx| {
24404        editor.handle_input("finally:", window, cx);
24405    });
24406    cx.assert_editor_state(indoc! {"
24407        def main():
24408            try:
24409                i = 2
24410            except:
24411                j = 2
24412            else:
24413                k = 2
24414            finally:ˇ
24415    "});
24416
24417    // test `else` does not outdents when typed inside `except` block right after for block
24418    cx.set_state(indoc! {"
24419        def main():
24420            try:
24421                i = 2
24422            except:
24423                for i in range(n):
24424                    pass
24425                ˇ
24426    "});
24427    cx.update_editor(|editor, window, cx| {
24428        editor.handle_input("else:", window, cx);
24429    });
24430    cx.assert_editor_state(indoc! {"
24431        def main():
24432            try:
24433                i = 2
24434            except:
24435                for i in range(n):
24436                    pass
24437                else:ˇ
24438    "});
24439
24440    // test `finally` auto outdents when typed inside `else` block right after for block
24441    cx.set_state(indoc! {"
24442        def main():
24443            try:
24444                i = 2
24445            except:
24446                j = 2
24447            else:
24448                for i in range(n):
24449                    pass
24450                ˇ
24451    "});
24452    cx.update_editor(|editor, window, cx| {
24453        editor.handle_input("finally:", window, cx);
24454    });
24455    cx.assert_editor_state(indoc! {"
24456        def main():
24457            try:
24458                i = 2
24459            except:
24460                j = 2
24461            else:
24462                for i in range(n):
24463                    pass
24464            finally:ˇ
24465    "});
24466
24467    // test `except` outdents to inner "try" block
24468    cx.set_state(indoc! {"
24469        def main():
24470            try:
24471                i = 2
24472                if i == 2:
24473                    try:
24474                        i = 3
24475                        ˇ
24476    "});
24477    cx.update_editor(|editor, window, cx| {
24478        editor.handle_input("except:", window, cx);
24479    });
24480    cx.assert_editor_state(indoc! {"
24481        def main():
24482            try:
24483                i = 2
24484                if i == 2:
24485                    try:
24486                        i = 3
24487                    except:ˇ
24488    "});
24489
24490    // test `except` outdents to outer "try" block
24491    cx.set_state(indoc! {"
24492        def main():
24493            try:
24494                i = 2
24495                if i == 2:
24496                    try:
24497                        i = 3
24498                ˇ
24499    "});
24500    cx.update_editor(|editor, window, cx| {
24501        editor.handle_input("except:", window, cx);
24502    });
24503    cx.assert_editor_state(indoc! {"
24504        def main():
24505            try:
24506                i = 2
24507                if i == 2:
24508                    try:
24509                        i = 3
24510            except:ˇ
24511    "});
24512
24513    // test `else` stays at correct indent when typed after `for` block
24514    cx.set_state(indoc! {"
24515        def main():
24516            for i in range(10):
24517                if i == 3:
24518                    break
24519            ˇ
24520    "});
24521    cx.update_editor(|editor, window, cx| {
24522        editor.handle_input("else:", window, cx);
24523    });
24524    cx.assert_editor_state(indoc! {"
24525        def main():
24526            for i in range(10):
24527                if i == 3:
24528                    break
24529            else:ˇ
24530    "});
24531
24532    // test does not outdent on typing after line with square brackets
24533    cx.set_state(indoc! {"
24534        def f() -> list[str]:
24535            ˇ
24536    "});
24537    cx.update_editor(|editor, window, cx| {
24538        editor.handle_input("a", window, cx);
24539    });
24540    cx.assert_editor_state(indoc! {"
24541        def f() -> list[str]:
2454224543    "});
24544
24545    // test does not outdent on typing : after case keyword
24546    cx.set_state(indoc! {"
24547        match 1:
24548            caseˇ
24549    "});
24550    cx.update_editor(|editor, window, cx| {
24551        editor.handle_input(":", window, cx);
24552    });
24553    cx.assert_editor_state(indoc! {"
24554        match 1:
24555            case:ˇ
24556    "});
24557}
24558
24559#[gpui::test]
24560async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
24561    init_test(cx, |_| {});
24562    update_test_language_settings(cx, |settings| {
24563        settings.defaults.extend_comment_on_newline = Some(false);
24564    });
24565    let mut cx = EditorTestContext::new(cx).await;
24566    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24567    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24568
24569    // test correct indent after newline on comment
24570    cx.set_state(indoc! {"
24571        # COMMENT:ˇ
24572    "});
24573    cx.update_editor(|editor, window, cx| {
24574        editor.newline(&Newline, window, cx);
24575    });
24576    cx.assert_editor_state(indoc! {"
24577        # COMMENT:
24578        ˇ
24579    "});
24580
24581    // test correct indent after newline in brackets
24582    cx.set_state(indoc! {"
24583        {ˇ}
24584    "});
24585    cx.update_editor(|editor, window, cx| {
24586        editor.newline(&Newline, window, cx);
24587    });
24588    cx.run_until_parked();
24589    cx.assert_editor_state(indoc! {"
24590        {
24591            ˇ
24592        }
24593    "});
24594
24595    cx.set_state(indoc! {"
24596        (ˇ)
24597    "});
24598    cx.update_editor(|editor, window, cx| {
24599        editor.newline(&Newline, window, cx);
24600    });
24601    cx.run_until_parked();
24602    cx.assert_editor_state(indoc! {"
24603        (
24604            ˇ
24605        )
24606    "});
24607
24608    // do not indent after empty lists or dictionaries
24609    cx.set_state(indoc! {"
24610        a = []ˇ
24611    "});
24612    cx.update_editor(|editor, window, cx| {
24613        editor.newline(&Newline, window, cx);
24614    });
24615    cx.run_until_parked();
24616    cx.assert_editor_state(indoc! {"
24617        a = []
24618        ˇ
24619    "});
24620}
24621
24622#[gpui::test]
24623async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
24624    init_test(cx, |_| {});
24625
24626    let mut cx = EditorTestContext::new(cx).await;
24627    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24628    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24629
24630    // test cursor move to start of each line on tab
24631    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
24632    cx.set_state(indoc! {"
24633        function main() {
24634        ˇ    for item in $items; do
24635        ˇ        while [ -n \"$item\" ]; do
24636        ˇ            if [ \"$value\" -gt 10 ]; then
24637        ˇ                continue
24638        ˇ            elif [ \"$value\" -lt 0 ]; then
24639        ˇ                break
24640        ˇ            else
24641        ˇ                echo \"$item\"
24642        ˇ            fi
24643        ˇ        done
24644        ˇ    done
24645        ˇ}
24646    "});
24647    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24648    cx.assert_editor_state(indoc! {"
24649        function main() {
24650            ˇfor item in $items; do
24651                ˇwhile [ -n \"$item\" ]; do
24652                    ˇif [ \"$value\" -gt 10 ]; then
24653                        ˇcontinue
24654                    ˇelif [ \"$value\" -lt 0 ]; then
24655                        ˇbreak
24656                    ˇelse
24657                        ˇecho \"$item\"
24658                    ˇfi
24659                ˇdone
24660            ˇdone
24661        ˇ}
24662    "});
24663    // test relative indent is preserved when tab
24664    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24665    cx.assert_editor_state(indoc! {"
24666        function main() {
24667                ˇfor item in $items; do
24668                    ˇwhile [ -n \"$item\" ]; do
24669                        ˇif [ \"$value\" -gt 10 ]; then
24670                            ˇcontinue
24671                        ˇelif [ \"$value\" -lt 0 ]; then
24672                            ˇbreak
24673                        ˇelse
24674                            ˇecho \"$item\"
24675                        ˇfi
24676                    ˇdone
24677                ˇdone
24678            ˇ}
24679    "});
24680
24681    // test cursor move to start of each line on tab
24682    // for `case` statement with patterns
24683    cx.set_state(indoc! {"
24684        function handle() {
24685        ˇ    case \"$1\" in
24686        ˇ        start)
24687        ˇ            echo \"a\"
24688        ˇ            ;;
24689        ˇ        stop)
24690        ˇ            echo \"b\"
24691        ˇ            ;;
24692        ˇ        *)
24693        ˇ            echo \"c\"
24694        ˇ            ;;
24695        ˇ    esac
24696        ˇ}
24697    "});
24698    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24699    cx.assert_editor_state(indoc! {"
24700        function handle() {
24701            ˇcase \"$1\" in
24702                ˇstart)
24703                    ˇecho \"a\"
24704                    ˇ;;
24705                ˇstop)
24706                    ˇecho \"b\"
24707                    ˇ;;
24708                ˇ*)
24709                    ˇecho \"c\"
24710                    ˇ;;
24711            ˇesac
24712        ˇ}
24713    "});
24714}
24715
24716#[gpui::test]
24717async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
24718    init_test(cx, |_| {});
24719
24720    let mut cx = EditorTestContext::new(cx).await;
24721    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24722    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24723
24724    // test indents on comment insert
24725    cx.set_state(indoc! {"
24726        function main() {
24727        ˇ    for item in $items; do
24728        ˇ        while [ -n \"$item\" ]; do
24729        ˇ            if [ \"$value\" -gt 10 ]; then
24730        ˇ                continue
24731        ˇ            elif [ \"$value\" -lt 0 ]; then
24732        ˇ                break
24733        ˇ            else
24734        ˇ                echo \"$item\"
24735        ˇ            fi
24736        ˇ        done
24737        ˇ    done
24738        ˇ}
24739    "});
24740    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
24741    cx.assert_editor_state(indoc! {"
24742        function main() {
24743        #ˇ    for item in $items; do
24744        #ˇ        while [ -n \"$item\" ]; do
24745        #ˇ            if [ \"$value\" -gt 10 ]; then
24746        #ˇ                continue
24747        #ˇ            elif [ \"$value\" -lt 0 ]; then
24748        #ˇ                break
24749        #ˇ            else
24750        #ˇ                echo \"$item\"
24751        #ˇ            fi
24752        #ˇ        done
24753        #ˇ    done
24754        #ˇ}
24755    "});
24756}
24757
24758#[gpui::test]
24759async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
24760    init_test(cx, |_| {});
24761
24762    let mut cx = EditorTestContext::new(cx).await;
24763    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24764    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24765
24766    // test `else` auto outdents when typed inside `if` block
24767    cx.set_state(indoc! {"
24768        if [ \"$1\" = \"test\" ]; then
24769            echo \"foo bar\"
24770            ˇ
24771    "});
24772    cx.update_editor(|editor, window, cx| {
24773        editor.handle_input("else", window, cx);
24774    });
24775    cx.assert_editor_state(indoc! {"
24776        if [ \"$1\" = \"test\" ]; then
24777            echo \"foo bar\"
24778        elseˇ
24779    "});
24780
24781    // test `elif` auto outdents when typed inside `if` block
24782    cx.set_state(indoc! {"
24783        if [ \"$1\" = \"test\" ]; then
24784            echo \"foo bar\"
24785            ˇ
24786    "});
24787    cx.update_editor(|editor, window, cx| {
24788        editor.handle_input("elif", window, cx);
24789    });
24790    cx.assert_editor_state(indoc! {"
24791        if [ \"$1\" = \"test\" ]; then
24792            echo \"foo bar\"
24793        elifˇ
24794    "});
24795
24796    // test `fi` auto outdents when typed inside `else` block
24797    cx.set_state(indoc! {"
24798        if [ \"$1\" = \"test\" ]; then
24799            echo \"foo bar\"
24800        else
24801            echo \"bar baz\"
24802            ˇ
24803    "});
24804    cx.update_editor(|editor, window, cx| {
24805        editor.handle_input("fi", window, cx);
24806    });
24807    cx.assert_editor_state(indoc! {"
24808        if [ \"$1\" = \"test\" ]; then
24809            echo \"foo bar\"
24810        else
24811            echo \"bar baz\"
24812        fiˇ
24813    "});
24814
24815    // test `done` auto outdents when typed inside `while` block
24816    cx.set_state(indoc! {"
24817        while read line; do
24818            echo \"$line\"
24819            ˇ
24820    "});
24821    cx.update_editor(|editor, window, cx| {
24822        editor.handle_input("done", window, cx);
24823    });
24824    cx.assert_editor_state(indoc! {"
24825        while read line; do
24826            echo \"$line\"
24827        doneˇ
24828    "});
24829
24830    // test `done` auto outdents when typed inside `for` block
24831    cx.set_state(indoc! {"
24832        for file in *.txt; do
24833            cat \"$file\"
24834            ˇ
24835    "});
24836    cx.update_editor(|editor, window, cx| {
24837        editor.handle_input("done", window, cx);
24838    });
24839    cx.assert_editor_state(indoc! {"
24840        for file in *.txt; do
24841            cat \"$file\"
24842        doneˇ
24843    "});
24844
24845    // test `esac` auto outdents when typed inside `case` block
24846    cx.set_state(indoc! {"
24847        case \"$1\" in
24848            start)
24849                echo \"foo bar\"
24850                ;;
24851            stop)
24852                echo \"bar baz\"
24853                ;;
24854            ˇ
24855    "});
24856    cx.update_editor(|editor, window, cx| {
24857        editor.handle_input("esac", window, cx);
24858    });
24859    cx.assert_editor_state(indoc! {"
24860        case \"$1\" in
24861            start)
24862                echo \"foo bar\"
24863                ;;
24864            stop)
24865                echo \"bar baz\"
24866                ;;
24867        esacˇ
24868    "});
24869
24870    // test `*)` auto outdents when typed inside `case` block
24871    cx.set_state(indoc! {"
24872        case \"$1\" in
24873            start)
24874                echo \"foo bar\"
24875                ;;
24876                ˇ
24877    "});
24878    cx.update_editor(|editor, window, cx| {
24879        editor.handle_input("*)", window, cx);
24880    });
24881    cx.assert_editor_state(indoc! {"
24882        case \"$1\" in
24883            start)
24884                echo \"foo bar\"
24885                ;;
24886            *)ˇ
24887    "});
24888
24889    // test `fi` outdents to correct level with nested if blocks
24890    cx.set_state(indoc! {"
24891        if [ \"$1\" = \"test\" ]; then
24892            echo \"outer if\"
24893            if [ \"$2\" = \"debug\" ]; then
24894                echo \"inner if\"
24895                ˇ
24896    "});
24897    cx.update_editor(|editor, window, cx| {
24898        editor.handle_input("fi", window, cx);
24899    });
24900    cx.assert_editor_state(indoc! {"
24901        if [ \"$1\" = \"test\" ]; then
24902            echo \"outer if\"
24903            if [ \"$2\" = \"debug\" ]; then
24904                echo \"inner if\"
24905            fiˇ
24906    "});
24907}
24908
24909#[gpui::test]
24910async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
24911    init_test(cx, |_| {});
24912    update_test_language_settings(cx, |settings| {
24913        settings.defaults.extend_comment_on_newline = Some(false);
24914    });
24915    let mut cx = EditorTestContext::new(cx).await;
24916    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24917    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24918
24919    // test correct indent after newline on comment
24920    cx.set_state(indoc! {"
24921        # COMMENT:ˇ
24922    "});
24923    cx.update_editor(|editor, window, cx| {
24924        editor.newline(&Newline, window, cx);
24925    });
24926    cx.assert_editor_state(indoc! {"
24927        # COMMENT:
24928        ˇ
24929    "});
24930
24931    // test correct indent after newline after `then`
24932    cx.set_state(indoc! {"
24933
24934        if [ \"$1\" = \"test\" ]; thenˇ
24935    "});
24936    cx.update_editor(|editor, window, cx| {
24937        editor.newline(&Newline, window, cx);
24938    });
24939    cx.run_until_parked();
24940    cx.assert_editor_state(indoc! {"
24941
24942        if [ \"$1\" = \"test\" ]; then
24943            ˇ
24944    "});
24945
24946    // test correct indent after newline after `else`
24947    cx.set_state(indoc! {"
24948        if [ \"$1\" = \"test\" ]; then
24949        elseˇ
24950    "});
24951    cx.update_editor(|editor, window, cx| {
24952        editor.newline(&Newline, window, cx);
24953    });
24954    cx.run_until_parked();
24955    cx.assert_editor_state(indoc! {"
24956        if [ \"$1\" = \"test\" ]; then
24957        else
24958            ˇ
24959    "});
24960
24961    // test correct indent after newline after `elif`
24962    cx.set_state(indoc! {"
24963        if [ \"$1\" = \"test\" ]; then
24964        elifˇ
24965    "});
24966    cx.update_editor(|editor, window, cx| {
24967        editor.newline(&Newline, window, cx);
24968    });
24969    cx.run_until_parked();
24970    cx.assert_editor_state(indoc! {"
24971        if [ \"$1\" = \"test\" ]; then
24972        elif
24973            ˇ
24974    "});
24975
24976    // test correct indent after newline after `do`
24977    cx.set_state(indoc! {"
24978        for file in *.txt; doˇ
24979    "});
24980    cx.update_editor(|editor, window, cx| {
24981        editor.newline(&Newline, window, cx);
24982    });
24983    cx.run_until_parked();
24984    cx.assert_editor_state(indoc! {"
24985        for file in *.txt; do
24986            ˇ
24987    "});
24988
24989    // test correct indent after newline after case pattern
24990    cx.set_state(indoc! {"
24991        case \"$1\" in
24992            start)ˇ
24993    "});
24994    cx.update_editor(|editor, window, cx| {
24995        editor.newline(&Newline, window, cx);
24996    });
24997    cx.run_until_parked();
24998    cx.assert_editor_state(indoc! {"
24999        case \"$1\" in
25000            start)
25001                ˇ
25002    "});
25003
25004    // test correct indent after newline after case pattern
25005    cx.set_state(indoc! {"
25006        case \"$1\" in
25007            start)
25008                ;;
25009            *)ˇ
25010    "});
25011    cx.update_editor(|editor, window, cx| {
25012        editor.newline(&Newline, window, cx);
25013    });
25014    cx.run_until_parked();
25015    cx.assert_editor_state(indoc! {"
25016        case \"$1\" in
25017            start)
25018                ;;
25019            *)
25020                ˇ
25021    "});
25022
25023    // test correct indent after newline after function opening brace
25024    cx.set_state(indoc! {"
25025        function test() {ˇ}
25026    "});
25027    cx.update_editor(|editor, window, cx| {
25028        editor.newline(&Newline, window, cx);
25029    });
25030    cx.run_until_parked();
25031    cx.assert_editor_state(indoc! {"
25032        function test() {
25033            ˇ
25034        }
25035    "});
25036
25037    // test no extra indent after semicolon on same line
25038    cx.set_state(indoc! {"
25039        echo \"test\"25040    "});
25041    cx.update_editor(|editor, window, cx| {
25042        editor.newline(&Newline, window, cx);
25043    });
25044    cx.run_until_parked();
25045    cx.assert_editor_state(indoc! {"
25046        echo \"test\";
25047        ˇ
25048    "});
25049}
25050
25051fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
25052    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
25053    point..point
25054}
25055
25056#[track_caller]
25057fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
25058    let (text, ranges) = marked_text_ranges(marked_text, true);
25059    assert_eq!(editor.text(cx), text);
25060    assert_eq!(
25061        editor.selections.ranges(cx),
25062        ranges,
25063        "Assert selections are {}",
25064        marked_text
25065    );
25066}
25067
25068pub fn handle_signature_help_request(
25069    cx: &mut EditorLspTestContext,
25070    mocked_response: lsp::SignatureHelp,
25071) -> impl Future<Output = ()> + use<> {
25072    let mut request =
25073        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
25074            let mocked_response = mocked_response.clone();
25075            async move { Ok(Some(mocked_response)) }
25076        });
25077
25078    async move {
25079        request.next().await;
25080    }
25081}
25082
25083#[track_caller]
25084pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
25085    cx.update_editor(|editor, _, _| {
25086        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
25087            let entries = menu.entries.borrow();
25088            let entries = entries
25089                .iter()
25090                .map(|entry| entry.string.as_str())
25091                .collect::<Vec<_>>();
25092            assert_eq!(entries, expected);
25093        } else {
25094            panic!("Expected completions menu");
25095        }
25096    });
25097}
25098
25099/// Handle completion request passing a marked string specifying where the completion
25100/// should be triggered from using '|' character, what range should be replaced, and what completions
25101/// should be returned using '<' and '>' to delimit the range.
25102///
25103/// Also see `handle_completion_request_with_insert_and_replace`.
25104#[track_caller]
25105pub fn handle_completion_request(
25106    marked_string: &str,
25107    completions: Vec<&'static str>,
25108    is_incomplete: bool,
25109    counter: Arc<AtomicUsize>,
25110    cx: &mut EditorLspTestContext,
25111) -> impl Future<Output = ()> {
25112    let complete_from_marker: TextRangeMarker = '|'.into();
25113    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25114    let (_, mut marked_ranges) = marked_text_ranges_by(
25115        marked_string,
25116        vec![complete_from_marker.clone(), replace_range_marker.clone()],
25117    );
25118
25119    let complete_from_position =
25120        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25121    let replace_range =
25122        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25123
25124    let mut request =
25125        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25126            let completions = completions.clone();
25127            counter.fetch_add(1, atomic::Ordering::Release);
25128            async move {
25129                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25130                assert_eq!(
25131                    params.text_document_position.position,
25132                    complete_from_position
25133                );
25134                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
25135                    is_incomplete,
25136                    item_defaults: None,
25137                    items: completions
25138                        .iter()
25139                        .map(|completion_text| lsp::CompletionItem {
25140                            label: completion_text.to_string(),
25141                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
25142                                range: replace_range,
25143                                new_text: completion_text.to_string(),
25144                            })),
25145                            ..Default::default()
25146                        })
25147                        .collect(),
25148                })))
25149            }
25150        });
25151
25152    async move {
25153        request.next().await;
25154    }
25155}
25156
25157/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
25158/// given instead, which also contains an `insert` range.
25159///
25160/// This function uses markers to define ranges:
25161/// - `|` marks the cursor position
25162/// - `<>` marks the replace range
25163/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
25164pub fn handle_completion_request_with_insert_and_replace(
25165    cx: &mut EditorLspTestContext,
25166    marked_string: &str,
25167    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
25168    counter: Arc<AtomicUsize>,
25169) -> impl Future<Output = ()> {
25170    let complete_from_marker: TextRangeMarker = '|'.into();
25171    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25172    let insert_range_marker: TextRangeMarker = ('{', '}').into();
25173
25174    let (_, mut marked_ranges) = marked_text_ranges_by(
25175        marked_string,
25176        vec![
25177            complete_from_marker.clone(),
25178            replace_range_marker.clone(),
25179            insert_range_marker.clone(),
25180        ],
25181    );
25182
25183    let complete_from_position =
25184        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25185    let replace_range =
25186        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25187
25188    let insert_range = match marked_ranges.remove(&insert_range_marker) {
25189        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
25190        _ => lsp::Range {
25191            start: replace_range.start,
25192            end: complete_from_position,
25193        },
25194    };
25195
25196    let mut request =
25197        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25198            let completions = completions.clone();
25199            counter.fetch_add(1, atomic::Ordering::Release);
25200            async move {
25201                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25202                assert_eq!(
25203                    params.text_document_position.position, complete_from_position,
25204                    "marker `|` position doesn't match",
25205                );
25206                Ok(Some(lsp::CompletionResponse::Array(
25207                    completions
25208                        .iter()
25209                        .map(|(label, new_text)| lsp::CompletionItem {
25210                            label: label.to_string(),
25211                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25212                                lsp::InsertReplaceEdit {
25213                                    insert: insert_range,
25214                                    replace: replace_range,
25215                                    new_text: new_text.to_string(),
25216                                },
25217                            )),
25218                            ..Default::default()
25219                        })
25220                        .collect(),
25221                )))
25222            }
25223        });
25224
25225    async move {
25226        request.next().await;
25227    }
25228}
25229
25230fn handle_resolve_completion_request(
25231    cx: &mut EditorLspTestContext,
25232    edits: Option<Vec<(&'static str, &'static str)>>,
25233) -> impl Future<Output = ()> {
25234    let edits = edits.map(|edits| {
25235        edits
25236            .iter()
25237            .map(|(marked_string, new_text)| {
25238                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
25239                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
25240                lsp::TextEdit::new(replace_range, new_text.to_string())
25241            })
25242            .collect::<Vec<_>>()
25243    });
25244
25245    let mut request =
25246        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
25247            let edits = edits.clone();
25248            async move {
25249                Ok(lsp::CompletionItem {
25250                    additional_text_edits: edits,
25251                    ..Default::default()
25252                })
25253            }
25254        });
25255
25256    async move {
25257        request.next().await;
25258    }
25259}
25260
25261pub(crate) fn update_test_language_settings(
25262    cx: &mut TestAppContext,
25263    f: impl Fn(&mut AllLanguageSettingsContent),
25264) {
25265    cx.update(|cx| {
25266        SettingsStore::update_global(cx, |store, cx| {
25267            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
25268        });
25269    });
25270}
25271
25272pub(crate) fn update_test_project_settings(
25273    cx: &mut TestAppContext,
25274    f: impl Fn(&mut ProjectSettingsContent),
25275) {
25276    cx.update(|cx| {
25277        SettingsStore::update_global(cx, |store, cx| {
25278            store.update_user_settings(cx, |settings| f(&mut settings.project));
25279        });
25280    });
25281}
25282
25283pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
25284    cx.update(|cx| {
25285        assets::Assets.load_test_fonts(cx);
25286        let store = SettingsStore::test(cx);
25287        cx.set_global(store);
25288        theme::init(theme::LoadThemes::JustBase, cx);
25289        release_channel::init(SemanticVersion::default(), cx);
25290        client::init_settings(cx);
25291        language::init(cx);
25292        Project::init_settings(cx);
25293        workspace::init_settings(cx);
25294        crate::init(cx);
25295    });
25296    zlog::init_test();
25297    update_test_language_settings(cx, f);
25298}
25299
25300#[track_caller]
25301fn assert_hunk_revert(
25302    not_reverted_text_with_selections: &str,
25303    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
25304    expected_reverted_text_with_selections: &str,
25305    base_text: &str,
25306    cx: &mut EditorLspTestContext,
25307) {
25308    cx.set_state(not_reverted_text_with_selections);
25309    cx.set_head_text(base_text);
25310    cx.executor().run_until_parked();
25311
25312    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
25313        let snapshot = editor.snapshot(window, cx);
25314        let reverted_hunk_statuses = snapshot
25315            .buffer_snapshot
25316            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
25317            .map(|hunk| hunk.status().kind)
25318            .collect::<Vec<_>>();
25319
25320        editor.git_restore(&Default::default(), window, cx);
25321        reverted_hunk_statuses
25322    });
25323    cx.executor().run_until_parked();
25324    cx.assert_editor_state(expected_reverted_text_with_selections);
25325    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
25326}
25327
25328#[gpui::test(iterations = 10)]
25329async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
25330    init_test(cx, |_| {});
25331
25332    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
25333    let counter = diagnostic_requests.clone();
25334
25335    let fs = FakeFs::new(cx.executor());
25336    fs.insert_tree(
25337        path!("/a"),
25338        json!({
25339            "first.rs": "fn main() { let a = 5; }",
25340            "second.rs": "// Test file",
25341        }),
25342    )
25343    .await;
25344
25345    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25346    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25347    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25348
25349    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25350    language_registry.add(rust_lang());
25351    let mut fake_servers = language_registry.register_fake_lsp(
25352        "Rust",
25353        FakeLspAdapter {
25354            capabilities: lsp::ServerCapabilities {
25355                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
25356                    lsp::DiagnosticOptions {
25357                        identifier: None,
25358                        inter_file_dependencies: true,
25359                        workspace_diagnostics: true,
25360                        work_done_progress_options: Default::default(),
25361                    },
25362                )),
25363                ..Default::default()
25364            },
25365            ..Default::default()
25366        },
25367    );
25368
25369    let editor = workspace
25370        .update(cx, |workspace, window, cx| {
25371            workspace.open_abs_path(
25372                PathBuf::from(path!("/a/first.rs")),
25373                OpenOptions::default(),
25374                window,
25375                cx,
25376            )
25377        })
25378        .unwrap()
25379        .await
25380        .unwrap()
25381        .downcast::<Editor>()
25382        .unwrap();
25383    let fake_server = fake_servers.next().await.unwrap();
25384    let server_id = fake_server.server.server_id();
25385    let mut first_request = fake_server
25386        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
25387            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
25388            let result_id = Some(new_result_id.to_string());
25389            assert_eq!(
25390                params.text_document.uri,
25391                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25392            );
25393            async move {
25394                Ok(lsp::DocumentDiagnosticReportResult::Report(
25395                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
25396                        related_documents: None,
25397                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
25398                            items: Vec::new(),
25399                            result_id,
25400                        },
25401                    }),
25402                ))
25403            }
25404        });
25405
25406    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
25407        project.update(cx, |project, cx| {
25408            let buffer_id = editor
25409                .read(cx)
25410                .buffer()
25411                .read(cx)
25412                .as_singleton()
25413                .expect("created a singleton buffer")
25414                .read(cx)
25415                .remote_id();
25416            let buffer_result_id = project
25417                .lsp_store()
25418                .read(cx)
25419                .result_id(server_id, buffer_id, cx);
25420            assert_eq!(expected, buffer_result_id);
25421        });
25422    };
25423
25424    ensure_result_id(None, cx);
25425    cx.executor().advance_clock(Duration::from_millis(60));
25426    cx.executor().run_until_parked();
25427    assert_eq!(
25428        diagnostic_requests.load(atomic::Ordering::Acquire),
25429        1,
25430        "Opening file should trigger diagnostic request"
25431    );
25432    first_request
25433        .next()
25434        .await
25435        .expect("should have sent the first diagnostics pull request");
25436    ensure_result_id(Some("1".to_string()), cx);
25437
25438    // Editing should trigger diagnostics
25439    editor.update_in(cx, |editor, window, cx| {
25440        editor.handle_input("2", window, cx)
25441    });
25442    cx.executor().advance_clock(Duration::from_millis(60));
25443    cx.executor().run_until_parked();
25444    assert_eq!(
25445        diagnostic_requests.load(atomic::Ordering::Acquire),
25446        2,
25447        "Editing should trigger diagnostic request"
25448    );
25449    ensure_result_id(Some("2".to_string()), cx);
25450
25451    // Moving cursor should not trigger diagnostic request
25452    editor.update_in(cx, |editor, window, cx| {
25453        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25454            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
25455        });
25456    });
25457    cx.executor().advance_clock(Duration::from_millis(60));
25458    cx.executor().run_until_parked();
25459    assert_eq!(
25460        diagnostic_requests.load(atomic::Ordering::Acquire),
25461        2,
25462        "Cursor movement should not trigger diagnostic request"
25463    );
25464    ensure_result_id(Some("2".to_string()), cx);
25465    // Multiple rapid edits should be debounced
25466    for _ in 0..5 {
25467        editor.update_in(cx, |editor, window, cx| {
25468            editor.handle_input("x", window, cx)
25469        });
25470    }
25471    cx.executor().advance_clock(Duration::from_millis(60));
25472    cx.executor().run_until_parked();
25473
25474    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
25475    assert!(
25476        final_requests <= 4,
25477        "Multiple rapid edits should be debounced (got {final_requests} requests)",
25478    );
25479    ensure_result_id(Some(final_requests.to_string()), cx);
25480}
25481
25482#[gpui::test]
25483async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
25484    // Regression test for issue #11671
25485    // Previously, adding a cursor after moving multiple cursors would reset
25486    // the cursor count instead of adding to the existing cursors.
25487    init_test(cx, |_| {});
25488    let mut cx = EditorTestContext::new(cx).await;
25489
25490    // Create a simple buffer with cursor at start
25491    cx.set_state(indoc! {"
25492        ˇaaaa
25493        bbbb
25494        cccc
25495        dddd
25496        eeee
25497        ffff
25498        gggg
25499        hhhh"});
25500
25501    // Add 2 cursors below (so we have 3 total)
25502    cx.update_editor(|editor, window, cx| {
25503        editor.add_selection_below(&Default::default(), window, cx);
25504        editor.add_selection_below(&Default::default(), window, cx);
25505    });
25506
25507    // Verify we have 3 cursors
25508    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
25509    assert_eq!(
25510        initial_count, 3,
25511        "Should have 3 cursors after adding 2 below"
25512    );
25513
25514    // Move down one line
25515    cx.update_editor(|editor, window, cx| {
25516        editor.move_down(&MoveDown, window, cx);
25517    });
25518
25519    // Add another cursor below
25520    cx.update_editor(|editor, window, cx| {
25521        editor.add_selection_below(&Default::default(), window, cx);
25522    });
25523
25524    // Should now have 4 cursors (3 original + 1 new)
25525    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
25526    assert_eq!(
25527        final_count, 4,
25528        "Should have 4 cursors after moving and adding another"
25529    );
25530}
25531
25532#[gpui::test(iterations = 10)]
25533async fn test_document_colors(cx: &mut TestAppContext) {
25534    let expected_color = Rgba {
25535        r: 0.33,
25536        g: 0.33,
25537        b: 0.33,
25538        a: 0.33,
25539    };
25540
25541    init_test(cx, |_| {});
25542
25543    let fs = FakeFs::new(cx.executor());
25544    fs.insert_tree(
25545        path!("/a"),
25546        json!({
25547            "first.rs": "fn main() { let a = 5; }",
25548        }),
25549    )
25550    .await;
25551
25552    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25553    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25554    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25555
25556    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25557    language_registry.add(rust_lang());
25558    let mut fake_servers = language_registry.register_fake_lsp(
25559        "Rust",
25560        FakeLspAdapter {
25561            capabilities: lsp::ServerCapabilities {
25562                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
25563                ..lsp::ServerCapabilities::default()
25564            },
25565            name: "rust-analyzer",
25566            ..FakeLspAdapter::default()
25567        },
25568    );
25569    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
25570        "Rust",
25571        FakeLspAdapter {
25572            capabilities: lsp::ServerCapabilities {
25573                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
25574                ..lsp::ServerCapabilities::default()
25575            },
25576            name: "not-rust-analyzer",
25577            ..FakeLspAdapter::default()
25578        },
25579    );
25580
25581    let editor = workspace
25582        .update(cx, |workspace, window, cx| {
25583            workspace.open_abs_path(
25584                PathBuf::from(path!("/a/first.rs")),
25585                OpenOptions::default(),
25586                window,
25587                cx,
25588            )
25589        })
25590        .unwrap()
25591        .await
25592        .unwrap()
25593        .downcast::<Editor>()
25594        .unwrap();
25595    let fake_language_server = fake_servers.next().await.unwrap();
25596    let fake_language_server_without_capabilities =
25597        fake_servers_without_capabilities.next().await.unwrap();
25598    let requests_made = Arc::new(AtomicUsize::new(0));
25599    let closure_requests_made = Arc::clone(&requests_made);
25600    let mut color_request_handle = fake_language_server
25601        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25602            let requests_made = Arc::clone(&closure_requests_made);
25603            async move {
25604                assert_eq!(
25605                    params.text_document.uri,
25606                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25607                );
25608                requests_made.fetch_add(1, atomic::Ordering::Release);
25609                Ok(vec![
25610                    lsp::ColorInformation {
25611                        range: lsp::Range {
25612                            start: lsp::Position {
25613                                line: 0,
25614                                character: 0,
25615                            },
25616                            end: lsp::Position {
25617                                line: 0,
25618                                character: 1,
25619                            },
25620                        },
25621                        color: lsp::Color {
25622                            red: 0.33,
25623                            green: 0.33,
25624                            blue: 0.33,
25625                            alpha: 0.33,
25626                        },
25627                    },
25628                    lsp::ColorInformation {
25629                        range: lsp::Range {
25630                            start: lsp::Position {
25631                                line: 0,
25632                                character: 0,
25633                            },
25634                            end: lsp::Position {
25635                                line: 0,
25636                                character: 1,
25637                            },
25638                        },
25639                        color: lsp::Color {
25640                            red: 0.33,
25641                            green: 0.33,
25642                            blue: 0.33,
25643                            alpha: 0.33,
25644                        },
25645                    },
25646                ])
25647            }
25648        });
25649
25650    let _handle = fake_language_server_without_capabilities
25651        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
25652            panic!("Should not be called");
25653        });
25654    cx.executor().advance_clock(Duration::from_millis(100));
25655    color_request_handle.next().await.unwrap();
25656    cx.run_until_parked();
25657    assert_eq!(
25658        1,
25659        requests_made.load(atomic::Ordering::Acquire),
25660        "Should query for colors once per editor open"
25661    );
25662    editor.update_in(cx, |editor, _, cx| {
25663        assert_eq!(
25664            vec![expected_color],
25665            extract_color_inlays(editor, cx),
25666            "Should have an initial inlay"
25667        );
25668    });
25669
25670    // opening another file in a split should not influence the LSP query counter
25671    workspace
25672        .update(cx, |workspace, window, cx| {
25673            assert_eq!(
25674                workspace.panes().len(),
25675                1,
25676                "Should have one pane with one editor"
25677            );
25678            workspace.move_item_to_pane_in_direction(
25679                &MoveItemToPaneInDirection {
25680                    direction: SplitDirection::Right,
25681                    focus: false,
25682                    clone: true,
25683                },
25684                window,
25685                cx,
25686            );
25687        })
25688        .unwrap();
25689    cx.run_until_parked();
25690    workspace
25691        .update(cx, |workspace, _, cx| {
25692            let panes = workspace.panes();
25693            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
25694            for pane in panes {
25695                let editor = pane
25696                    .read(cx)
25697                    .active_item()
25698                    .and_then(|item| item.downcast::<Editor>())
25699                    .expect("Should have opened an editor in each split");
25700                let editor_file = editor
25701                    .read(cx)
25702                    .buffer()
25703                    .read(cx)
25704                    .as_singleton()
25705                    .expect("test deals with singleton buffers")
25706                    .read(cx)
25707                    .file()
25708                    .expect("test buffese should have a file")
25709                    .path();
25710                assert_eq!(
25711                    editor_file.as_ref(),
25712                    rel_path("first.rs"),
25713                    "Both editors should be opened for the same file"
25714                )
25715            }
25716        })
25717        .unwrap();
25718
25719    cx.executor().advance_clock(Duration::from_millis(500));
25720    let save = editor.update_in(cx, |editor, window, cx| {
25721        editor.move_to_end(&MoveToEnd, window, cx);
25722        editor.handle_input("dirty", window, cx);
25723        editor.save(
25724            SaveOptions {
25725                format: true,
25726                autosave: true,
25727            },
25728            project.clone(),
25729            window,
25730            cx,
25731        )
25732    });
25733    save.await.unwrap();
25734
25735    color_request_handle.next().await.unwrap();
25736    cx.run_until_parked();
25737    assert_eq!(
25738        3,
25739        requests_made.load(atomic::Ordering::Acquire),
25740        "Should query for colors once per save and once per formatting after save"
25741    );
25742
25743    drop(editor);
25744    let close = workspace
25745        .update(cx, |workspace, window, cx| {
25746            workspace.active_pane().update(cx, |pane, cx| {
25747                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25748            })
25749        })
25750        .unwrap();
25751    close.await.unwrap();
25752    let close = workspace
25753        .update(cx, |workspace, window, cx| {
25754            workspace.active_pane().update(cx, |pane, cx| {
25755                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25756            })
25757        })
25758        .unwrap();
25759    close.await.unwrap();
25760    assert_eq!(
25761        3,
25762        requests_made.load(atomic::Ordering::Acquire),
25763        "After saving and closing all editors, no extra requests should be made"
25764    );
25765    workspace
25766        .update(cx, |workspace, _, cx| {
25767            assert!(
25768                workspace.active_item(cx).is_none(),
25769                "Should close all editors"
25770            )
25771        })
25772        .unwrap();
25773
25774    workspace
25775        .update(cx, |workspace, window, cx| {
25776            workspace.active_pane().update(cx, |pane, cx| {
25777                pane.navigate_backward(&workspace::GoBack, window, cx);
25778            })
25779        })
25780        .unwrap();
25781    cx.executor().advance_clock(Duration::from_millis(100));
25782    cx.run_until_parked();
25783    let editor = workspace
25784        .update(cx, |workspace, _, cx| {
25785            workspace
25786                .active_item(cx)
25787                .expect("Should have reopened the editor again after navigating back")
25788                .downcast::<Editor>()
25789                .expect("Should be an editor")
25790        })
25791        .unwrap();
25792    color_request_handle.next().await.unwrap();
25793    assert_eq!(
25794        3,
25795        requests_made.load(atomic::Ordering::Acquire),
25796        "Cache should be reused on buffer close and reopen"
25797    );
25798    editor.update(cx, |editor, cx| {
25799        assert_eq!(
25800            vec![expected_color],
25801            extract_color_inlays(editor, cx),
25802            "Should have an initial inlay"
25803        );
25804    });
25805
25806    drop(color_request_handle);
25807    let closure_requests_made = Arc::clone(&requests_made);
25808    let mut empty_color_request_handle = fake_language_server
25809        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25810            let requests_made = Arc::clone(&closure_requests_made);
25811            async move {
25812                assert_eq!(
25813                    params.text_document.uri,
25814                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25815                );
25816                requests_made.fetch_add(1, atomic::Ordering::Release);
25817                Ok(Vec::new())
25818            }
25819        });
25820    let save = editor.update_in(cx, |editor, window, cx| {
25821        editor.move_to_end(&MoveToEnd, window, cx);
25822        editor.handle_input("dirty_again", window, cx);
25823        editor.save(
25824            SaveOptions {
25825                format: false,
25826                autosave: true,
25827            },
25828            project.clone(),
25829            window,
25830            cx,
25831        )
25832    });
25833    save.await.unwrap();
25834
25835    empty_color_request_handle.next().await.unwrap();
25836    cx.run_until_parked();
25837    assert_eq!(
25838        4,
25839        requests_made.load(atomic::Ordering::Acquire),
25840        "Should query for colors once per save only, as formatting was not requested"
25841    );
25842    editor.update(cx, |editor, cx| {
25843        assert_eq!(
25844            Vec::<Rgba>::new(),
25845            extract_color_inlays(editor, cx),
25846            "Should clear all colors when the server returns an empty response"
25847        );
25848    });
25849}
25850
25851#[gpui::test]
25852async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
25853    init_test(cx, |_| {});
25854    let (editor, cx) = cx.add_window_view(Editor::single_line);
25855    editor.update_in(cx, |editor, window, cx| {
25856        editor.set_text("oops\n\nwow\n", window, cx)
25857    });
25858    cx.run_until_parked();
25859    editor.update(cx, |editor, cx| {
25860        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
25861    });
25862    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
25863    cx.run_until_parked();
25864    editor.update(cx, |editor, cx| {
25865        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
25866    });
25867}
25868
25869#[gpui::test]
25870async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
25871    init_test(cx, |_| {});
25872
25873    cx.update(|cx| {
25874        register_project_item::<Editor>(cx);
25875    });
25876
25877    let fs = FakeFs::new(cx.executor());
25878    fs.insert_tree("/root1", json!({})).await;
25879    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
25880        .await;
25881
25882    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
25883    let (workspace, cx) =
25884        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25885
25886    let worktree_id = project.update(cx, |project, cx| {
25887        project.worktrees(cx).next().unwrap().read(cx).id()
25888    });
25889
25890    let handle = workspace
25891        .update_in(cx, |workspace, window, cx| {
25892            let project_path = (worktree_id, rel_path("one.pdf"));
25893            workspace.open_path(project_path, None, true, window, cx)
25894        })
25895        .await
25896        .unwrap();
25897
25898    assert_eq!(
25899        handle.to_any().entity_type(),
25900        TypeId::of::<InvalidBufferView>()
25901    );
25902}
25903
25904#[gpui::test]
25905async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
25906    init_test(cx, |_| {});
25907
25908    let language = Arc::new(Language::new(
25909        LanguageConfig::default(),
25910        Some(tree_sitter_rust::LANGUAGE.into()),
25911    ));
25912
25913    // Test hierarchical sibling navigation
25914    let text = r#"
25915        fn outer() {
25916            if condition {
25917                let a = 1;
25918            }
25919            let b = 2;
25920        }
25921
25922        fn another() {
25923            let c = 3;
25924        }
25925    "#;
25926
25927    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
25928    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25929    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
25930
25931    // Wait for parsing to complete
25932    editor
25933        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
25934        .await;
25935
25936    editor.update_in(cx, |editor, window, cx| {
25937        // Start by selecting "let a = 1;" inside the if block
25938        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25939            s.select_display_ranges([
25940                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
25941            ]);
25942        });
25943
25944        let initial_selection = editor.selections.display_ranges(cx);
25945        assert_eq!(initial_selection.len(), 1, "Should have one selection");
25946
25947        // Test select next sibling - should move up levels to find the next sibling
25948        // Since "let a = 1;" has no siblings in the if block, it should move up
25949        // to find "let b = 2;" which is a sibling of the if block
25950        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
25951        let next_selection = editor.selections.display_ranges(cx);
25952
25953        // Should have a selection and it should be different from the initial
25954        assert_eq!(
25955            next_selection.len(),
25956            1,
25957            "Should have one selection after next"
25958        );
25959        assert_ne!(
25960            next_selection[0], initial_selection[0],
25961            "Next sibling selection should be different"
25962        );
25963
25964        // Test hierarchical navigation by going to the end of the current function
25965        // and trying to navigate to the next function
25966        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25967            s.select_display_ranges([
25968                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
25969            ]);
25970        });
25971
25972        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
25973        let function_next_selection = editor.selections.display_ranges(cx);
25974
25975        // Should move to the next function
25976        assert_eq!(
25977            function_next_selection.len(),
25978            1,
25979            "Should have one selection after function next"
25980        );
25981
25982        // Test select previous sibling navigation
25983        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
25984        let prev_selection = editor.selections.display_ranges(cx);
25985
25986        // Should have a selection and it should be different
25987        assert_eq!(
25988            prev_selection.len(),
25989            1,
25990            "Should have one selection after prev"
25991        );
25992        assert_ne!(
25993            prev_selection[0], function_next_selection[0],
25994            "Previous sibling selection should be different from next"
25995        );
25996    });
25997}
25998
25999#[gpui::test]
26000async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
26001    init_test(cx, |_| {});
26002
26003    let mut cx = EditorTestContext::new(cx).await;
26004    cx.set_state(
26005        "let ˇvariable = 42;
26006let another = variable + 1;
26007let result = variable * 2;",
26008    );
26009
26010    // Set up document highlights manually (simulating LSP response)
26011    cx.update_editor(|editor, _window, cx| {
26012        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
26013
26014        // Create highlights for "variable" occurrences
26015        let highlight_ranges = [
26016            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
26017            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
26018            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
26019        ];
26020
26021        let anchor_ranges: Vec<_> = highlight_ranges
26022            .iter()
26023            .map(|range| range.clone().to_anchors(&buffer_snapshot))
26024            .collect();
26025
26026        editor.highlight_background::<DocumentHighlightRead>(
26027            &anchor_ranges,
26028            |theme| theme.colors().editor_document_highlight_read_background,
26029            cx,
26030        );
26031    });
26032
26033    // Go to next highlight - should move to second "variable"
26034    cx.update_editor(|editor, window, cx| {
26035        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26036    });
26037    cx.assert_editor_state(
26038        "let variable = 42;
26039let another = ˇvariable + 1;
26040let result = variable * 2;",
26041    );
26042
26043    // Go to next highlight - should move to third "variable"
26044    cx.update_editor(|editor, window, cx| {
26045        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26046    });
26047    cx.assert_editor_state(
26048        "let variable = 42;
26049let another = variable + 1;
26050let result = ˇvariable * 2;",
26051    );
26052
26053    // Go to next highlight - should stay at third "variable" (no wrap-around)
26054    cx.update_editor(|editor, window, cx| {
26055        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26056    });
26057    cx.assert_editor_state(
26058        "let variable = 42;
26059let another = variable + 1;
26060let result = ˇvariable * 2;",
26061    );
26062
26063    // Now test going backwards from third position
26064    cx.update_editor(|editor, window, cx| {
26065        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26066    });
26067    cx.assert_editor_state(
26068        "let variable = 42;
26069let another = ˇvariable + 1;
26070let result = variable * 2;",
26071    );
26072
26073    // Go to previous highlight - should move to first "variable"
26074    cx.update_editor(|editor, window, cx| {
26075        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26076    });
26077    cx.assert_editor_state(
26078        "let ˇvariable = 42;
26079let another = variable + 1;
26080let result = variable * 2;",
26081    );
26082
26083    // Go to previous highlight - should stay on first "variable"
26084    cx.update_editor(|editor, window, cx| {
26085        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26086    });
26087    cx.assert_editor_state(
26088        "let ˇvariable = 42;
26089let another = variable + 1;
26090let result = variable * 2;",
26091    );
26092}
26093
26094#[gpui::test]
26095async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
26096    cx: &mut gpui::TestAppContext,
26097) {
26098    init_test(cx, |_| {});
26099
26100    let url = "https://zed.dev";
26101
26102    let markdown_language = Arc::new(Language::new(
26103        LanguageConfig {
26104            name: "Markdown".into(),
26105            ..LanguageConfig::default()
26106        },
26107        None,
26108    ));
26109
26110    let mut cx = EditorTestContext::new(cx).await;
26111    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26112    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
26113
26114    cx.update_editor(|editor, window, cx| {
26115        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26116        editor.paste(&Paste, window, cx);
26117    });
26118
26119    cx.assert_editor_state(&format!(
26120        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
26121    ));
26122}
26123
26124#[gpui::test]
26125async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
26126    cx: &mut gpui::TestAppContext,
26127) {
26128    init_test(cx, |_| {});
26129
26130    let url = "https://zed.dev";
26131
26132    let markdown_language = Arc::new(Language::new(
26133        LanguageConfig {
26134            name: "Markdown".into(),
26135            ..LanguageConfig::default()
26136        },
26137        None,
26138    ));
26139
26140    let mut cx = EditorTestContext::new(cx).await;
26141    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26142    cx.set_state(&format!(
26143        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
26144    ));
26145
26146    cx.update_editor(|editor, window, cx| {
26147        editor.copy(&Copy, window, cx);
26148    });
26149
26150    cx.set_state(&format!(
26151        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
26152    ));
26153
26154    cx.update_editor(|editor, window, cx| {
26155        editor.paste(&Paste, window, cx);
26156    });
26157
26158    cx.assert_editor_state(&format!(
26159        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
26160    ));
26161}
26162
26163#[gpui::test]
26164async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
26165    cx: &mut gpui::TestAppContext,
26166) {
26167    init_test(cx, |_| {});
26168
26169    let url = "https://zed.dev";
26170
26171    let markdown_language = Arc::new(Language::new(
26172        LanguageConfig {
26173            name: "Markdown".into(),
26174            ..LanguageConfig::default()
26175        },
26176        None,
26177    ));
26178
26179    let mut cx = EditorTestContext::new(cx).await;
26180    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26181    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
26182
26183    cx.update_editor(|editor, window, cx| {
26184        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26185        editor.paste(&Paste, window, cx);
26186    });
26187
26188    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
26189}
26190
26191#[gpui::test]
26192async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
26193    cx: &mut gpui::TestAppContext,
26194) {
26195    init_test(cx, |_| {});
26196
26197    let text = "Awesome";
26198
26199    let markdown_language = Arc::new(Language::new(
26200        LanguageConfig {
26201            name: "Markdown".into(),
26202            ..LanguageConfig::default()
26203        },
26204        None,
26205    ));
26206
26207    let mut cx = EditorTestContext::new(cx).await;
26208    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26209    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
26210
26211    cx.update_editor(|editor, window, cx| {
26212        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
26213        editor.paste(&Paste, window, cx);
26214    });
26215
26216    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
26217}
26218
26219#[gpui::test]
26220async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
26221    cx: &mut gpui::TestAppContext,
26222) {
26223    init_test(cx, |_| {});
26224
26225    let url = "https://zed.dev";
26226
26227    let markdown_language = Arc::new(Language::new(
26228        LanguageConfig {
26229            name: "Rust".into(),
26230            ..LanguageConfig::default()
26231        },
26232        None,
26233    ));
26234
26235    let mut cx = EditorTestContext::new(cx).await;
26236    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26237    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
26238
26239    cx.update_editor(|editor, window, cx| {
26240        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26241        editor.paste(&Paste, window, cx);
26242    });
26243
26244    cx.assert_editor_state(&format!(
26245        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
26246    ));
26247}
26248
26249#[gpui::test]
26250async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
26251    cx: &mut TestAppContext,
26252) {
26253    init_test(cx, |_| {});
26254
26255    let url = "https://zed.dev";
26256
26257    let markdown_language = Arc::new(Language::new(
26258        LanguageConfig {
26259            name: "Markdown".into(),
26260            ..LanguageConfig::default()
26261        },
26262        None,
26263    ));
26264
26265    let (editor, cx) = cx.add_window_view(|window, cx| {
26266        let multi_buffer = MultiBuffer::build_multi(
26267            [
26268                ("this will embed -> link", vec![Point::row_range(0..1)]),
26269                ("this will replace -> link", vec![Point::row_range(0..1)]),
26270            ],
26271            cx,
26272        );
26273        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
26274        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26275            s.select_ranges(vec![
26276                Point::new(0, 19)..Point::new(0, 23),
26277                Point::new(1, 21)..Point::new(1, 25),
26278            ])
26279        });
26280        let first_buffer_id = multi_buffer
26281            .read(cx)
26282            .excerpt_buffer_ids()
26283            .into_iter()
26284            .next()
26285            .unwrap();
26286        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
26287        first_buffer.update(cx, |buffer, cx| {
26288            buffer.set_language(Some(markdown_language.clone()), cx);
26289        });
26290
26291        editor
26292    });
26293    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
26294
26295    cx.update_editor(|editor, window, cx| {
26296        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26297        editor.paste(&Paste, window, cx);
26298    });
26299
26300    cx.assert_editor_state(&format!(
26301        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
26302    ));
26303}
26304
26305#[track_caller]
26306fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
26307    editor
26308        .all_inlays(cx)
26309        .into_iter()
26310        .filter_map(|inlay| inlay.get_color())
26311        .map(Rgba::from)
26312        .collect()
26313}