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! {"
 6746        The quick« brownˇ»
 6747        fox jumps overˇ
 6748        the lazy dog"});
 6749    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6750    cx.assert_editor_state(indoc! {"
 6751        The quickˇ
 6752        ˇthe lazy dog"});
 6753
 6754    cx.set_state(indoc! {"
 6755        The quick« brownˇ»
 6756        fox jumps overˇ
 6757        the lazy dog"});
 6758    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 6759    cx.assert_editor_state(indoc! {"
 6760        The quickˇ
 6761        fox jumps overˇthe lazy dog"});
 6762
 6763    cx.set_state(indoc! {"
 6764        The quick« brownˇ»
 6765        fox jumps overˇ
 6766        the lazy dog"});
 6767    cx.update_editor(|e, window, cx| {
 6768        e.cut_to_end_of_line(
 6769            &CutToEndOfLine {
 6770                stop_at_newlines: true,
 6771            },
 6772            window,
 6773            cx,
 6774        )
 6775    });
 6776    cx.assert_editor_state(indoc! {"
 6777        The quickˇ
 6778        fox jumps overˇ
 6779        the lazy dog"});
 6780
 6781    cx.set_state(indoc! {"
 6782        The quick« brownˇ»
 6783        fox jumps overˇ
 6784        the lazy dog"});
 6785    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 6786    cx.assert_editor_state(indoc! {"
 6787        The quickˇ
 6788        fox jumps overˇthe lazy dog"});
 6789}
 6790
 6791#[gpui::test]
 6792async fn test_clipboard(cx: &mut TestAppContext) {
 6793    init_test(cx, |_| {});
 6794
 6795    let mut cx = EditorTestContext::new(cx).await;
 6796
 6797    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 6798    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6799    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 6800
 6801    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 6802    cx.set_state("two ˇfour ˇsix ˇ");
 6803    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6804    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 6805
 6806    // Paste again but with only two cursors. Since the number of cursors doesn't
 6807    // match the number of slices in the clipboard, the entire clipboard text
 6808    // is pasted at each cursor.
 6809    cx.set_state("ˇtwo one✅ four three six five ˇ");
 6810    cx.update_editor(|e, window, cx| {
 6811        e.handle_input("( ", window, cx);
 6812        e.paste(&Paste, window, cx);
 6813        e.handle_input(") ", window, cx);
 6814    });
 6815    cx.assert_editor_state(
 6816        &([
 6817            "( one✅ ",
 6818            "three ",
 6819            "five ) ˇtwo one✅ four three six five ( one✅ ",
 6820            "three ",
 6821            "five ) ˇ",
 6822        ]
 6823        .join("\n")),
 6824    );
 6825
 6826    // Cut with three selections, one of which is full-line.
 6827    cx.set_state(indoc! {"
 6828        1«2ˇ»3
 6829        4ˇ567
 6830        «8ˇ»9"});
 6831    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6832    cx.assert_editor_state(indoc! {"
 6833        1ˇ3
 6834        ˇ9"});
 6835
 6836    // Paste with three selections, noticing how the copied selection that was full-line
 6837    // gets inserted before the second cursor.
 6838    cx.set_state(indoc! {"
 6839        1ˇ3
 6840 6841        «oˇ»ne"});
 6842    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6843    cx.assert_editor_state(indoc! {"
 6844        12ˇ3
 6845        4567
 6846 6847        8ˇne"});
 6848
 6849    // Copy with a single cursor only, which writes the whole line into the clipboard.
 6850    cx.set_state(indoc! {"
 6851        The quick brown
 6852        fox juˇmps over
 6853        the lazy dog"});
 6854    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6855    assert_eq!(
 6856        cx.read_from_clipboard()
 6857            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6858        Some("fox jumps over\n".to_string())
 6859    );
 6860
 6861    // Paste with three selections, noticing how the copied full-line selection is inserted
 6862    // before the empty selections but replaces the selection that is non-empty.
 6863    cx.set_state(indoc! {"
 6864        Tˇhe quick brown
 6865        «foˇ»x jumps over
 6866        tˇhe lazy dog"});
 6867    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6868    cx.assert_editor_state(indoc! {"
 6869        fox jumps over
 6870        Tˇhe quick brown
 6871        fox jumps over
 6872        ˇx jumps over
 6873        fox jumps over
 6874        tˇhe lazy dog"});
 6875}
 6876
 6877#[gpui::test]
 6878async fn test_copy_trim(cx: &mut TestAppContext) {
 6879    init_test(cx, |_| {});
 6880
 6881    let mut cx = EditorTestContext::new(cx).await;
 6882    cx.set_state(
 6883        r#"            «for selection in selections.iter() {
 6884            let mut start = selection.start;
 6885            let mut end = selection.end;
 6886            let is_entire_line = selection.is_empty();
 6887            if is_entire_line {
 6888                start = Point::new(start.row, 0);ˇ»
 6889                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6890            }
 6891        "#,
 6892    );
 6893    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6894    assert_eq!(
 6895        cx.read_from_clipboard()
 6896            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6897        Some(
 6898            "for selection in selections.iter() {
 6899            let mut start = selection.start;
 6900            let mut end = selection.end;
 6901            let is_entire_line = selection.is_empty();
 6902            if is_entire_line {
 6903                start = Point::new(start.row, 0);"
 6904                .to_string()
 6905        ),
 6906        "Regular copying preserves all indentation selected",
 6907    );
 6908    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6909    assert_eq!(
 6910        cx.read_from_clipboard()
 6911            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6912        Some(
 6913            "for selection in selections.iter() {
 6914let mut start = selection.start;
 6915let mut end = selection.end;
 6916let is_entire_line = selection.is_empty();
 6917if is_entire_line {
 6918    start = Point::new(start.row, 0);"
 6919                .to_string()
 6920        ),
 6921        "Copying with stripping should strip all leading whitespaces"
 6922    );
 6923
 6924    cx.set_state(
 6925        r#"       «     for selection in selections.iter() {
 6926            let mut start = selection.start;
 6927            let mut end = selection.end;
 6928            let is_entire_line = selection.is_empty();
 6929            if is_entire_line {
 6930                start = Point::new(start.row, 0);ˇ»
 6931                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6932            }
 6933        "#,
 6934    );
 6935    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6936    assert_eq!(
 6937        cx.read_from_clipboard()
 6938            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6939        Some(
 6940            "     for selection in selections.iter() {
 6941            let mut start = selection.start;
 6942            let mut end = selection.end;
 6943            let is_entire_line = selection.is_empty();
 6944            if is_entire_line {
 6945                start = Point::new(start.row, 0);"
 6946                .to_string()
 6947        ),
 6948        "Regular copying preserves all indentation selected",
 6949    );
 6950    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6951    assert_eq!(
 6952        cx.read_from_clipboard()
 6953            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6954        Some(
 6955            "for selection in selections.iter() {
 6956let mut start = selection.start;
 6957let mut end = selection.end;
 6958let is_entire_line = selection.is_empty();
 6959if is_entire_line {
 6960    start = Point::new(start.row, 0);"
 6961                .to_string()
 6962        ),
 6963        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 6964    );
 6965
 6966    cx.set_state(
 6967        r#"       «ˇ     for selection in selections.iter() {
 6968            let mut start = selection.start;
 6969            let mut end = selection.end;
 6970            let is_entire_line = selection.is_empty();
 6971            if is_entire_line {
 6972                start = Point::new(start.row, 0);»
 6973                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6974            }
 6975        "#,
 6976    );
 6977    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6978    assert_eq!(
 6979        cx.read_from_clipboard()
 6980            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6981        Some(
 6982            "     for selection in selections.iter() {
 6983            let mut start = selection.start;
 6984            let mut end = selection.end;
 6985            let is_entire_line = selection.is_empty();
 6986            if is_entire_line {
 6987                start = Point::new(start.row, 0);"
 6988                .to_string()
 6989        ),
 6990        "Regular copying for reverse selection works the same",
 6991    );
 6992    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6993    assert_eq!(
 6994        cx.read_from_clipboard()
 6995            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6996        Some(
 6997            "for selection in selections.iter() {
 6998let mut start = selection.start;
 6999let mut end = selection.end;
 7000let is_entire_line = selection.is_empty();
 7001if is_entire_line {
 7002    start = Point::new(start.row, 0);"
 7003                .to_string()
 7004        ),
 7005        "Copying with stripping for reverse selection works the same"
 7006    );
 7007
 7008    cx.set_state(
 7009        r#"            for selection «in selections.iter() {
 7010            let mut start = selection.start;
 7011            let mut end = selection.end;
 7012            let is_entire_line = selection.is_empty();
 7013            if is_entire_line {
 7014                start = Point::new(start.row, 0);ˇ»
 7015                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7016            }
 7017        "#,
 7018    );
 7019    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7020    assert_eq!(
 7021        cx.read_from_clipboard()
 7022            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7023        Some(
 7024            "in selections.iter() {
 7025            let mut start = selection.start;
 7026            let mut end = selection.end;
 7027            let is_entire_line = selection.is_empty();
 7028            if is_entire_line {
 7029                start = Point::new(start.row, 0);"
 7030                .to_string()
 7031        ),
 7032        "When selecting past the indent, the copying works as usual",
 7033    );
 7034    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7035    assert_eq!(
 7036        cx.read_from_clipboard()
 7037            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7038        Some(
 7039            "in selections.iter() {
 7040            let mut start = selection.start;
 7041            let mut end = selection.end;
 7042            let is_entire_line = selection.is_empty();
 7043            if is_entire_line {
 7044                start = Point::new(start.row, 0);"
 7045                .to_string()
 7046        ),
 7047        "When selecting past the indent, nothing is trimmed"
 7048    );
 7049
 7050    cx.set_state(
 7051        r#"            «for selection in selections.iter() {
 7052            let mut start = selection.start;
 7053
 7054            let mut end = selection.end;
 7055            let is_entire_line = selection.is_empty();
 7056            if is_entire_line {
 7057                start = Point::new(start.row, 0);
 7058ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7059            }
 7060        "#,
 7061    );
 7062    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7063    assert_eq!(
 7064        cx.read_from_clipboard()
 7065            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7066        Some(
 7067            "for selection in selections.iter() {
 7068let mut start = selection.start;
 7069
 7070let mut end = selection.end;
 7071let is_entire_line = selection.is_empty();
 7072if is_entire_line {
 7073    start = Point::new(start.row, 0);
 7074"
 7075            .to_string()
 7076        ),
 7077        "Copying with stripping should ignore empty lines"
 7078    );
 7079}
 7080
 7081#[gpui::test]
 7082async fn test_paste_multiline(cx: &mut TestAppContext) {
 7083    init_test(cx, |_| {});
 7084
 7085    let mut cx = EditorTestContext::new(cx).await;
 7086    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7087
 7088    // Cut an indented block, without the leading whitespace.
 7089    cx.set_state(indoc! {"
 7090        const a: B = (
 7091            c(),
 7092            «d(
 7093                e,
 7094                f
 7095            )ˇ»
 7096        );
 7097    "});
 7098    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7099    cx.assert_editor_state(indoc! {"
 7100        const a: B = (
 7101            c(),
 7102            ˇ
 7103        );
 7104    "});
 7105
 7106    // Paste it at the same position.
 7107    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7108    cx.assert_editor_state(indoc! {"
 7109        const a: B = (
 7110            c(),
 7111            d(
 7112                e,
 7113                f
 7114 7115        );
 7116    "});
 7117
 7118    // Paste it at a line with a lower indent level.
 7119    cx.set_state(indoc! {"
 7120        ˇ
 7121        const a: B = (
 7122            c(),
 7123        );
 7124    "});
 7125    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7126    cx.assert_editor_state(indoc! {"
 7127        d(
 7128            e,
 7129            f
 7130 7131        const a: B = (
 7132            c(),
 7133        );
 7134    "});
 7135
 7136    // Cut an indented block, with the leading whitespace.
 7137    cx.set_state(indoc! {"
 7138        const a: B = (
 7139            c(),
 7140        «    d(
 7141                e,
 7142                f
 7143            )
 7144        ˇ»);
 7145    "});
 7146    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7147    cx.assert_editor_state(indoc! {"
 7148        const a: B = (
 7149            c(),
 7150        ˇ);
 7151    "});
 7152
 7153    // Paste it at the same position.
 7154    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7155    cx.assert_editor_state(indoc! {"
 7156        const a: B = (
 7157            c(),
 7158            d(
 7159                e,
 7160                f
 7161            )
 7162        ˇ);
 7163    "});
 7164
 7165    // Paste it at a line with a higher indent level.
 7166    cx.set_state(indoc! {"
 7167        const a: B = (
 7168            c(),
 7169            d(
 7170                e,
 7171 7172            )
 7173        );
 7174    "});
 7175    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7176    cx.assert_editor_state(indoc! {"
 7177        const a: B = (
 7178            c(),
 7179            d(
 7180                e,
 7181                f    d(
 7182                    e,
 7183                    f
 7184                )
 7185        ˇ
 7186            )
 7187        );
 7188    "});
 7189
 7190    // Copy an indented block, starting mid-line
 7191    cx.set_state(indoc! {"
 7192        const a: B = (
 7193            c(),
 7194            somethin«g(
 7195                e,
 7196                f
 7197            )ˇ»
 7198        );
 7199    "});
 7200    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7201
 7202    // Paste it on a line with a lower indent level
 7203    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7204    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7205    cx.assert_editor_state(indoc! {"
 7206        const a: B = (
 7207            c(),
 7208            something(
 7209                e,
 7210                f
 7211            )
 7212        );
 7213        g(
 7214            e,
 7215            f
 7216"});
 7217}
 7218
 7219#[gpui::test]
 7220async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7221    init_test(cx, |_| {});
 7222
 7223    cx.write_to_clipboard(ClipboardItem::new_string(
 7224        "    d(\n        e\n    );\n".into(),
 7225    ));
 7226
 7227    let mut cx = EditorTestContext::new(cx).await;
 7228    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7229
 7230    cx.set_state(indoc! {"
 7231        fn a() {
 7232            b();
 7233            if c() {
 7234                ˇ
 7235            }
 7236        }
 7237    "});
 7238
 7239    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7240    cx.assert_editor_state(indoc! {"
 7241        fn a() {
 7242            b();
 7243            if c() {
 7244                d(
 7245                    e
 7246                );
 7247        ˇ
 7248            }
 7249        }
 7250    "});
 7251
 7252    cx.set_state(indoc! {"
 7253        fn a() {
 7254            b();
 7255            ˇ
 7256        }
 7257    "});
 7258
 7259    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7260    cx.assert_editor_state(indoc! {"
 7261        fn a() {
 7262            b();
 7263            d(
 7264                e
 7265            );
 7266        ˇ
 7267        }
 7268    "});
 7269}
 7270
 7271#[gpui::test]
 7272fn test_select_all(cx: &mut TestAppContext) {
 7273    init_test(cx, |_| {});
 7274
 7275    let editor = cx.add_window(|window, cx| {
 7276        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7277        build_editor(buffer, window, cx)
 7278    });
 7279    _ = editor.update(cx, |editor, window, cx| {
 7280        editor.select_all(&SelectAll, window, cx);
 7281        assert_eq!(
 7282            editor.selections.display_ranges(cx),
 7283            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7284        );
 7285    });
 7286}
 7287
 7288#[gpui::test]
 7289fn test_select_line(cx: &mut TestAppContext) {
 7290    init_test(cx, |_| {});
 7291
 7292    let editor = cx.add_window(|window, cx| {
 7293        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7294        build_editor(buffer, window, cx)
 7295    });
 7296    _ = editor.update(cx, |editor, window, cx| {
 7297        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7298            s.select_display_ranges([
 7299                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7300                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7301                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7302                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7303            ])
 7304        });
 7305        editor.select_line(&SelectLine, window, cx);
 7306        assert_eq!(
 7307            editor.selections.display_ranges(cx),
 7308            vec![
 7309                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7310                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7311            ]
 7312        );
 7313    });
 7314
 7315    _ = editor.update(cx, |editor, window, cx| {
 7316        editor.select_line(&SelectLine, window, cx);
 7317        assert_eq!(
 7318            editor.selections.display_ranges(cx),
 7319            vec![
 7320                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7321                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7322            ]
 7323        );
 7324    });
 7325
 7326    _ = editor.update(cx, |editor, window, cx| {
 7327        editor.select_line(&SelectLine, window, cx);
 7328        assert_eq!(
 7329            editor.selections.display_ranges(cx),
 7330            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 7331        );
 7332    });
 7333}
 7334
 7335#[gpui::test]
 7336async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7337    init_test(cx, |_| {});
 7338    let mut cx = EditorTestContext::new(cx).await;
 7339
 7340    #[track_caller]
 7341    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7342        cx.set_state(initial_state);
 7343        cx.update_editor(|e, window, cx| {
 7344            e.split_selection_into_lines(&Default::default(), window, cx)
 7345        });
 7346        cx.assert_editor_state(expected_state);
 7347    }
 7348
 7349    // Selection starts and ends at the middle of lines, left-to-right
 7350    test(
 7351        &mut cx,
 7352        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7353        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7354    );
 7355    // Same thing, right-to-left
 7356    test(
 7357        &mut cx,
 7358        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7359        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7360    );
 7361
 7362    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7363    test(
 7364        &mut cx,
 7365        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7366        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7367    );
 7368    // Same thing, right-to-left
 7369    test(
 7370        &mut cx,
 7371        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7372        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7373    );
 7374
 7375    // Whole buffer, left-to-right, last line ends with newline
 7376    test(
 7377        &mut cx,
 7378        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7379        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7380    );
 7381    // Same thing, right-to-left
 7382    test(
 7383        &mut cx,
 7384        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 7385        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7386    );
 7387
 7388    // Starts at the end of a line, ends at the start of another
 7389    test(
 7390        &mut cx,
 7391        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 7392        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 7393    );
 7394}
 7395
 7396#[gpui::test]
 7397async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 7398    init_test(cx, |_| {});
 7399
 7400    let editor = cx.add_window(|window, cx| {
 7401        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 7402        build_editor(buffer, window, cx)
 7403    });
 7404
 7405    // setup
 7406    _ = editor.update(cx, |editor, window, cx| {
 7407        editor.fold_creases(
 7408            vec![
 7409                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7410                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7411                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7412            ],
 7413            true,
 7414            window,
 7415            cx,
 7416        );
 7417        assert_eq!(
 7418            editor.display_text(cx),
 7419            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7420        );
 7421    });
 7422
 7423    _ = editor.update(cx, |editor, window, cx| {
 7424        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7425            s.select_display_ranges([
 7426                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7427                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7428                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7429                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7430            ])
 7431        });
 7432        editor.split_selection_into_lines(&Default::default(), window, cx);
 7433        assert_eq!(
 7434            editor.display_text(cx),
 7435            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7436        );
 7437    });
 7438    EditorTestContext::for_editor(editor, cx)
 7439        .await
 7440        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7441
 7442    _ = editor.update(cx, |editor, window, cx| {
 7443        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7444            s.select_display_ranges([
 7445                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7446            ])
 7447        });
 7448        editor.split_selection_into_lines(&Default::default(), window, cx);
 7449        assert_eq!(
 7450            editor.display_text(cx),
 7451            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7452        );
 7453        assert_eq!(
 7454            editor.selections.display_ranges(cx),
 7455            [
 7456                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7457                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7458                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7459                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7460                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7461                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7462                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7463            ]
 7464        );
 7465    });
 7466    EditorTestContext::for_editor(editor, cx)
 7467        .await
 7468        .assert_editor_state(
 7469            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7470        );
 7471}
 7472
 7473#[gpui::test]
 7474async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7475    init_test(cx, |_| {});
 7476
 7477    let mut cx = EditorTestContext::new(cx).await;
 7478
 7479    cx.set_state(indoc!(
 7480        r#"abc
 7481           defˇghi
 7482
 7483           jk
 7484           nlmo
 7485           "#
 7486    ));
 7487
 7488    cx.update_editor(|editor, window, cx| {
 7489        editor.add_selection_above(&Default::default(), window, cx);
 7490    });
 7491
 7492    cx.assert_editor_state(indoc!(
 7493        r#"abcˇ
 7494           defˇghi
 7495
 7496           jk
 7497           nlmo
 7498           "#
 7499    ));
 7500
 7501    cx.update_editor(|editor, window, cx| {
 7502        editor.add_selection_above(&Default::default(), window, cx);
 7503    });
 7504
 7505    cx.assert_editor_state(indoc!(
 7506        r#"abcˇ
 7507            defˇghi
 7508
 7509            jk
 7510            nlmo
 7511            "#
 7512    ));
 7513
 7514    cx.update_editor(|editor, window, cx| {
 7515        editor.add_selection_below(&Default::default(), window, cx);
 7516    });
 7517
 7518    cx.assert_editor_state(indoc!(
 7519        r#"abc
 7520           defˇghi
 7521
 7522           jk
 7523           nlmo
 7524           "#
 7525    ));
 7526
 7527    cx.update_editor(|editor, window, cx| {
 7528        editor.undo_selection(&Default::default(), window, cx);
 7529    });
 7530
 7531    cx.assert_editor_state(indoc!(
 7532        r#"abcˇ
 7533           defˇghi
 7534
 7535           jk
 7536           nlmo
 7537           "#
 7538    ));
 7539
 7540    cx.update_editor(|editor, window, cx| {
 7541        editor.redo_selection(&Default::default(), window, cx);
 7542    });
 7543
 7544    cx.assert_editor_state(indoc!(
 7545        r#"abc
 7546           defˇghi
 7547
 7548           jk
 7549           nlmo
 7550           "#
 7551    ));
 7552
 7553    cx.update_editor(|editor, window, cx| {
 7554        editor.add_selection_below(&Default::default(), window, cx);
 7555    });
 7556
 7557    cx.assert_editor_state(indoc!(
 7558        r#"abc
 7559           defˇghi
 7560           ˇ
 7561           jk
 7562           nlmo
 7563           "#
 7564    ));
 7565
 7566    cx.update_editor(|editor, window, cx| {
 7567        editor.add_selection_below(&Default::default(), window, cx);
 7568    });
 7569
 7570    cx.assert_editor_state(indoc!(
 7571        r#"abc
 7572           defˇghi
 7573           ˇ
 7574           jkˇ
 7575           nlmo
 7576           "#
 7577    ));
 7578
 7579    cx.update_editor(|editor, window, cx| {
 7580        editor.add_selection_below(&Default::default(), window, cx);
 7581    });
 7582
 7583    cx.assert_editor_state(indoc!(
 7584        r#"abc
 7585           defˇghi
 7586           ˇ
 7587           jkˇ
 7588           nlmˇo
 7589           "#
 7590    ));
 7591
 7592    cx.update_editor(|editor, window, cx| {
 7593        editor.add_selection_below(&Default::default(), window, cx);
 7594    });
 7595
 7596    cx.assert_editor_state(indoc!(
 7597        r#"abc
 7598           defˇghi
 7599           ˇ
 7600           jkˇ
 7601           nlmˇo
 7602           ˇ"#
 7603    ));
 7604
 7605    // change selections
 7606    cx.set_state(indoc!(
 7607        r#"abc
 7608           def«ˇg»hi
 7609
 7610           jk
 7611           nlmo
 7612           "#
 7613    ));
 7614
 7615    cx.update_editor(|editor, window, cx| {
 7616        editor.add_selection_below(&Default::default(), window, cx);
 7617    });
 7618
 7619    cx.assert_editor_state(indoc!(
 7620        r#"abc
 7621           def«ˇg»hi
 7622
 7623           jk
 7624           nlm«ˇo»
 7625           "#
 7626    ));
 7627
 7628    cx.update_editor(|editor, window, cx| {
 7629        editor.add_selection_below(&Default::default(), window, cx);
 7630    });
 7631
 7632    cx.assert_editor_state(indoc!(
 7633        r#"abc
 7634           def«ˇg»hi
 7635
 7636           jk
 7637           nlm«ˇo»
 7638           "#
 7639    ));
 7640
 7641    cx.update_editor(|editor, window, cx| {
 7642        editor.add_selection_above(&Default::default(), window, cx);
 7643    });
 7644
 7645    cx.assert_editor_state(indoc!(
 7646        r#"abc
 7647           def«ˇg»hi
 7648
 7649           jk
 7650           nlmo
 7651           "#
 7652    ));
 7653
 7654    cx.update_editor(|editor, window, cx| {
 7655        editor.add_selection_above(&Default::default(), window, cx);
 7656    });
 7657
 7658    cx.assert_editor_state(indoc!(
 7659        r#"abc
 7660           def«ˇg»hi
 7661
 7662           jk
 7663           nlmo
 7664           "#
 7665    ));
 7666
 7667    // Change selections again
 7668    cx.set_state(indoc!(
 7669        r#"a«bc
 7670           defgˇ»hi
 7671
 7672           jk
 7673           nlmo
 7674           "#
 7675    ));
 7676
 7677    cx.update_editor(|editor, window, cx| {
 7678        editor.add_selection_below(&Default::default(), window, cx);
 7679    });
 7680
 7681    cx.assert_editor_state(indoc!(
 7682        r#"a«bcˇ»
 7683           d«efgˇ»hi
 7684
 7685           j«kˇ»
 7686           nlmo
 7687           "#
 7688    ));
 7689
 7690    cx.update_editor(|editor, window, cx| {
 7691        editor.add_selection_below(&Default::default(), window, cx);
 7692    });
 7693    cx.assert_editor_state(indoc!(
 7694        r#"a«bcˇ»
 7695           d«efgˇ»hi
 7696
 7697           j«kˇ»
 7698           n«lmoˇ»
 7699           "#
 7700    ));
 7701    cx.update_editor(|editor, window, cx| {
 7702        editor.add_selection_above(&Default::default(), window, cx);
 7703    });
 7704
 7705    cx.assert_editor_state(indoc!(
 7706        r#"a«bcˇ»
 7707           d«efgˇ»hi
 7708
 7709           j«kˇ»
 7710           nlmo
 7711           "#
 7712    ));
 7713
 7714    // Change selections again
 7715    cx.set_state(indoc!(
 7716        r#"abc
 7717           d«ˇefghi
 7718
 7719           jk
 7720           nlm»o
 7721           "#
 7722    ));
 7723
 7724    cx.update_editor(|editor, window, cx| {
 7725        editor.add_selection_above(&Default::default(), window, cx);
 7726    });
 7727
 7728    cx.assert_editor_state(indoc!(
 7729        r#"a«ˇbc»
 7730           d«ˇef»ghi
 7731
 7732           j«ˇk»
 7733           n«ˇlm»o
 7734           "#
 7735    ));
 7736
 7737    cx.update_editor(|editor, window, cx| {
 7738        editor.add_selection_below(&Default::default(), window, cx);
 7739    });
 7740
 7741    cx.assert_editor_state(indoc!(
 7742        r#"abc
 7743           d«ˇef»ghi
 7744
 7745           j«ˇk»
 7746           n«ˇlm»o
 7747           "#
 7748    ));
 7749}
 7750
 7751#[gpui::test]
 7752async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 7753    init_test(cx, |_| {});
 7754    let mut cx = EditorTestContext::new(cx).await;
 7755
 7756    cx.set_state(indoc!(
 7757        r#"line onˇe
 7758           liˇne two
 7759           line three
 7760           line four"#
 7761    ));
 7762
 7763    cx.update_editor(|editor, window, cx| {
 7764        editor.add_selection_below(&Default::default(), window, cx);
 7765    });
 7766
 7767    // test multiple cursors expand in the same direction
 7768    cx.assert_editor_state(indoc!(
 7769        r#"line onˇe
 7770           liˇne twˇo
 7771           liˇne three
 7772           line four"#
 7773    ));
 7774
 7775    cx.update_editor(|editor, window, cx| {
 7776        editor.add_selection_below(&Default::default(), window, cx);
 7777    });
 7778
 7779    cx.update_editor(|editor, window, cx| {
 7780        editor.add_selection_below(&Default::default(), window, cx);
 7781    });
 7782
 7783    // test multiple cursors expand below overflow
 7784    cx.assert_editor_state(indoc!(
 7785        r#"line onˇe
 7786           liˇne twˇo
 7787           liˇne thˇree
 7788           liˇne foˇur"#
 7789    ));
 7790
 7791    cx.update_editor(|editor, window, cx| {
 7792        editor.add_selection_above(&Default::default(), window, cx);
 7793    });
 7794
 7795    // test multiple cursors retrieves back correctly
 7796    cx.assert_editor_state(indoc!(
 7797        r#"line onˇe
 7798           liˇne twˇo
 7799           liˇne thˇree
 7800           line four"#
 7801    ));
 7802
 7803    cx.update_editor(|editor, window, cx| {
 7804        editor.add_selection_above(&Default::default(), window, cx);
 7805    });
 7806
 7807    cx.update_editor(|editor, window, cx| {
 7808        editor.add_selection_above(&Default::default(), window, cx);
 7809    });
 7810
 7811    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 7812    cx.assert_editor_state(indoc!(
 7813        r#"liˇne onˇe
 7814           liˇne two
 7815           line three
 7816           line four"#
 7817    ));
 7818
 7819    cx.update_editor(|editor, window, cx| {
 7820        editor.undo_selection(&Default::default(), window, cx);
 7821    });
 7822
 7823    // test undo
 7824    cx.assert_editor_state(indoc!(
 7825        r#"line onˇe
 7826           liˇne twˇo
 7827           line three
 7828           line four"#
 7829    ));
 7830
 7831    cx.update_editor(|editor, window, cx| {
 7832        editor.redo_selection(&Default::default(), window, cx);
 7833    });
 7834
 7835    // test redo
 7836    cx.assert_editor_state(indoc!(
 7837        r#"liˇne onˇe
 7838           liˇne two
 7839           line three
 7840           line four"#
 7841    ));
 7842
 7843    cx.set_state(indoc!(
 7844        r#"abcd
 7845           ef«ghˇ»
 7846           ijkl
 7847           «mˇ»nop"#
 7848    ));
 7849
 7850    cx.update_editor(|editor, window, cx| {
 7851        editor.add_selection_above(&Default::default(), window, cx);
 7852    });
 7853
 7854    // test multiple selections expand in the same direction
 7855    cx.assert_editor_state(indoc!(
 7856        r#"ab«cdˇ»
 7857           ef«ghˇ»
 7858           «iˇ»jkl
 7859           «mˇ»nop"#
 7860    ));
 7861
 7862    cx.update_editor(|editor, window, cx| {
 7863        editor.add_selection_above(&Default::default(), window, cx);
 7864    });
 7865
 7866    // test multiple selection upward overflow
 7867    cx.assert_editor_state(indoc!(
 7868        r#"ab«cdˇ»
 7869           «eˇ»f«ghˇ»
 7870           «iˇ»jkl
 7871           «mˇ»nop"#
 7872    ));
 7873
 7874    cx.update_editor(|editor, window, cx| {
 7875        editor.add_selection_below(&Default::default(), window, cx);
 7876    });
 7877
 7878    // test multiple selection retrieves back correctly
 7879    cx.assert_editor_state(indoc!(
 7880        r#"abcd
 7881           ef«ghˇ»
 7882           «iˇ»jkl
 7883           «mˇ»nop"#
 7884    ));
 7885
 7886    cx.update_editor(|editor, window, cx| {
 7887        editor.add_selection_below(&Default::default(), window, cx);
 7888    });
 7889
 7890    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 7891    cx.assert_editor_state(indoc!(
 7892        r#"abcd
 7893           ef«ghˇ»
 7894           ij«klˇ»
 7895           «mˇ»nop"#
 7896    ));
 7897
 7898    cx.update_editor(|editor, window, cx| {
 7899        editor.undo_selection(&Default::default(), window, cx);
 7900    });
 7901
 7902    // test undo
 7903    cx.assert_editor_state(indoc!(
 7904        r#"abcd
 7905           ef«ghˇ»
 7906           «iˇ»jkl
 7907           «mˇ»nop"#
 7908    ));
 7909
 7910    cx.update_editor(|editor, window, cx| {
 7911        editor.redo_selection(&Default::default(), window, cx);
 7912    });
 7913
 7914    // test redo
 7915    cx.assert_editor_state(indoc!(
 7916        r#"abcd
 7917           ef«ghˇ»
 7918           ij«klˇ»
 7919           «mˇ»nop"#
 7920    ));
 7921}
 7922
 7923#[gpui::test]
 7924async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 7925    init_test(cx, |_| {});
 7926    let mut cx = EditorTestContext::new(cx).await;
 7927
 7928    cx.set_state(indoc!(
 7929        r#"line onˇe
 7930           liˇne two
 7931           line three
 7932           line four"#
 7933    ));
 7934
 7935    cx.update_editor(|editor, window, cx| {
 7936        editor.add_selection_below(&Default::default(), window, cx);
 7937        editor.add_selection_below(&Default::default(), window, cx);
 7938        editor.add_selection_below(&Default::default(), window, cx);
 7939    });
 7940
 7941    // initial state with two multi cursor groups
 7942    cx.assert_editor_state(indoc!(
 7943        r#"line onˇe
 7944           liˇne twˇo
 7945           liˇne thˇree
 7946           liˇne foˇur"#
 7947    ));
 7948
 7949    // add single cursor in middle - simulate opt click
 7950    cx.update_editor(|editor, window, cx| {
 7951        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 7952        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 7953        editor.end_selection(window, cx);
 7954    });
 7955
 7956    cx.assert_editor_state(indoc!(
 7957        r#"line onˇe
 7958           liˇne twˇo
 7959           liˇneˇ thˇree
 7960           liˇne foˇur"#
 7961    ));
 7962
 7963    cx.update_editor(|editor, window, cx| {
 7964        editor.add_selection_above(&Default::default(), window, cx);
 7965    });
 7966
 7967    // test new added selection expands above and existing selection shrinks
 7968    cx.assert_editor_state(indoc!(
 7969        r#"line onˇe
 7970           liˇneˇ twˇo
 7971           liˇneˇ thˇree
 7972           line four"#
 7973    ));
 7974
 7975    cx.update_editor(|editor, window, cx| {
 7976        editor.add_selection_above(&Default::default(), window, cx);
 7977    });
 7978
 7979    // test new added selection expands above and existing selection shrinks
 7980    cx.assert_editor_state(indoc!(
 7981        r#"lineˇ onˇe
 7982           liˇneˇ twˇo
 7983           lineˇ three
 7984           line four"#
 7985    ));
 7986
 7987    // intial state with two selection groups
 7988    cx.set_state(indoc!(
 7989        r#"abcd
 7990           ef«ghˇ»
 7991           ijkl
 7992           «mˇ»nop"#
 7993    ));
 7994
 7995    cx.update_editor(|editor, window, cx| {
 7996        editor.add_selection_above(&Default::default(), window, cx);
 7997        editor.add_selection_above(&Default::default(), window, cx);
 7998    });
 7999
 8000    cx.assert_editor_state(indoc!(
 8001        r#"ab«cdˇ»
 8002           «eˇ»f«ghˇ»
 8003           «iˇ»jkl
 8004           «mˇ»nop"#
 8005    ));
 8006
 8007    // add single selection in middle - simulate opt drag
 8008    cx.update_editor(|editor, window, cx| {
 8009        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8010        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8011        editor.update_selection(
 8012            DisplayPoint::new(DisplayRow(2), 4),
 8013            0,
 8014            gpui::Point::<f32>::default(),
 8015            window,
 8016            cx,
 8017        );
 8018        editor.end_selection(window, cx);
 8019    });
 8020
 8021    cx.assert_editor_state(indoc!(
 8022        r#"ab«cdˇ»
 8023           «eˇ»f«ghˇ»
 8024           «iˇ»jk«lˇ»
 8025           «mˇ»nop"#
 8026    ));
 8027
 8028    cx.update_editor(|editor, window, cx| {
 8029        editor.add_selection_below(&Default::default(), window, cx);
 8030    });
 8031
 8032    // test new added selection expands below, others shrinks from above
 8033    cx.assert_editor_state(indoc!(
 8034        r#"abcd
 8035           ef«ghˇ»
 8036           «iˇ»jk«lˇ»
 8037           «mˇ»no«pˇ»"#
 8038    ));
 8039}
 8040
 8041#[gpui::test]
 8042async fn test_select_next(cx: &mut TestAppContext) {
 8043    init_test(cx, |_| {});
 8044
 8045    let mut cx = EditorTestContext::new(cx).await;
 8046    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8047
 8048    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8049        .unwrap();
 8050    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8051
 8052    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8053        .unwrap();
 8054    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8055
 8056    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8057    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8058
 8059    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8060    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8061
 8062    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8063        .unwrap();
 8064    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8065
 8066    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8067        .unwrap();
 8068    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8069
 8070    // Test selection direction should be preserved
 8071    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8072
 8073    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8074        .unwrap();
 8075    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8076}
 8077
 8078#[gpui::test]
 8079async fn test_select_all_matches(cx: &mut TestAppContext) {
 8080    init_test(cx, |_| {});
 8081
 8082    let mut cx = EditorTestContext::new(cx).await;
 8083
 8084    // Test caret-only selections
 8085    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8086    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8087        .unwrap();
 8088    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8089
 8090    // Test left-to-right selections
 8091    cx.set_state("abc\n«abcˇ»\nabc");
 8092    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8093        .unwrap();
 8094    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8095
 8096    // Test right-to-left selections
 8097    cx.set_state("abc\n«ˇabc»\nabc");
 8098    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8099        .unwrap();
 8100    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8101
 8102    // Test selecting whitespace with caret selection
 8103    cx.set_state("abc\nˇ   abc\nabc");
 8104    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8105        .unwrap();
 8106    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8107
 8108    // Test selecting whitespace with left-to-right selection
 8109    cx.set_state("abc\n«ˇ  »abc\nabc");
 8110    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8111        .unwrap();
 8112    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8113
 8114    // Test no matches with right-to-left selection
 8115    cx.set_state("abc\n«  ˇ»abc\nabc");
 8116    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8117        .unwrap();
 8118    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8119
 8120    // Test with a single word and clip_at_line_ends=true (#29823)
 8121    cx.set_state("aˇbc");
 8122    cx.update_editor(|e, window, cx| {
 8123        e.set_clip_at_line_ends(true, cx);
 8124        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8125        e.set_clip_at_line_ends(false, cx);
 8126    });
 8127    cx.assert_editor_state("«abcˇ»");
 8128}
 8129
 8130#[gpui::test]
 8131async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8132    init_test(cx, |_| {});
 8133
 8134    let mut cx = EditorTestContext::new(cx).await;
 8135
 8136    let large_body_1 = "\nd".repeat(200);
 8137    let large_body_2 = "\ne".repeat(200);
 8138
 8139    cx.set_state(&format!(
 8140        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8141    ));
 8142    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8143        let scroll_position = editor.scroll_position(cx);
 8144        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8145        scroll_position
 8146    });
 8147
 8148    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8149        .unwrap();
 8150    cx.assert_editor_state(&format!(
 8151        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8152    ));
 8153    let scroll_position_after_selection =
 8154        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8155    assert_eq!(
 8156        initial_scroll_position, scroll_position_after_selection,
 8157        "Scroll position should not change after selecting all matches"
 8158    );
 8159}
 8160
 8161#[gpui::test]
 8162async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8163    init_test(cx, |_| {});
 8164
 8165    let mut cx = EditorLspTestContext::new_rust(
 8166        lsp::ServerCapabilities {
 8167            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8168            ..Default::default()
 8169        },
 8170        cx,
 8171    )
 8172    .await;
 8173
 8174    cx.set_state(indoc! {"
 8175        line 1
 8176        line 2
 8177        linˇe 3
 8178        line 4
 8179        line 5
 8180    "});
 8181
 8182    // Make an edit
 8183    cx.update_editor(|editor, window, cx| {
 8184        editor.handle_input("X", window, cx);
 8185    });
 8186
 8187    // Move cursor to a different position
 8188    cx.update_editor(|editor, window, cx| {
 8189        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8190            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8191        });
 8192    });
 8193
 8194    cx.assert_editor_state(indoc! {"
 8195        line 1
 8196        line 2
 8197        linXe 3
 8198        line 4
 8199        liˇne 5
 8200    "});
 8201
 8202    cx.lsp
 8203        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8204            Ok(Some(vec![lsp::TextEdit::new(
 8205                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8206                "PREFIX ".to_string(),
 8207            )]))
 8208        });
 8209
 8210    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8211        .unwrap()
 8212        .await
 8213        .unwrap();
 8214
 8215    cx.assert_editor_state(indoc! {"
 8216        PREFIX line 1
 8217        line 2
 8218        linXe 3
 8219        line 4
 8220        liˇne 5
 8221    "});
 8222
 8223    // Undo formatting
 8224    cx.update_editor(|editor, window, cx| {
 8225        editor.undo(&Default::default(), window, cx);
 8226    });
 8227
 8228    // Verify cursor moved back to position after edit
 8229    cx.assert_editor_state(indoc! {"
 8230        line 1
 8231        line 2
 8232        linXˇe 3
 8233        line 4
 8234        line 5
 8235    "});
 8236}
 8237
 8238#[gpui::test]
 8239async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8240    init_test(cx, |_| {});
 8241
 8242    let mut cx = EditorTestContext::new(cx).await;
 8243
 8244    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 8245    cx.update_editor(|editor, window, cx| {
 8246        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8247    });
 8248
 8249    cx.set_state(indoc! {"
 8250        line 1
 8251        line 2
 8252        linˇe 3
 8253        line 4
 8254        line 5
 8255        line 6
 8256        line 7
 8257        line 8
 8258        line 9
 8259        line 10
 8260    "});
 8261
 8262    let snapshot = cx.buffer_snapshot();
 8263    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8264
 8265    cx.update(|_, cx| {
 8266        provider.update(cx, |provider, _| {
 8267            provider.set_edit_prediction(Some(edit_prediction::EditPrediction {
 8268                id: None,
 8269                edits: vec![(edit_position..edit_position, "X".into())],
 8270                edit_preview: None,
 8271            }))
 8272        })
 8273    });
 8274
 8275    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8276    cx.update_editor(|editor, window, cx| {
 8277        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8278    });
 8279
 8280    cx.assert_editor_state(indoc! {"
 8281        line 1
 8282        line 2
 8283        lineXˇ 3
 8284        line 4
 8285        line 5
 8286        line 6
 8287        line 7
 8288        line 8
 8289        line 9
 8290        line 10
 8291    "});
 8292
 8293    cx.update_editor(|editor, window, cx| {
 8294        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8295            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8296        });
 8297    });
 8298
 8299    cx.assert_editor_state(indoc! {"
 8300        line 1
 8301        line 2
 8302        lineX 3
 8303        line 4
 8304        line 5
 8305        line 6
 8306        line 7
 8307        line 8
 8308        line 9
 8309        liˇne 10
 8310    "});
 8311
 8312    cx.update_editor(|editor, window, cx| {
 8313        editor.undo(&Default::default(), window, cx);
 8314    });
 8315
 8316    cx.assert_editor_state(indoc! {"
 8317        line 1
 8318        line 2
 8319        lineˇ 3
 8320        line 4
 8321        line 5
 8322        line 6
 8323        line 7
 8324        line 8
 8325        line 9
 8326        line 10
 8327    "});
 8328}
 8329
 8330#[gpui::test]
 8331async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8332    init_test(cx, |_| {});
 8333
 8334    let mut cx = EditorTestContext::new(cx).await;
 8335    cx.set_state(
 8336        r#"let foo = 2;
 8337lˇet foo = 2;
 8338let fooˇ = 2;
 8339let foo = 2;
 8340let foo = ˇ2;"#,
 8341    );
 8342
 8343    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8344        .unwrap();
 8345    cx.assert_editor_state(
 8346        r#"let foo = 2;
 8347«letˇ» foo = 2;
 8348let «fooˇ» = 2;
 8349let foo = 2;
 8350let foo = «2ˇ»;"#,
 8351    );
 8352
 8353    // noop for multiple selections with different contents
 8354    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8355        .unwrap();
 8356    cx.assert_editor_state(
 8357        r#"let foo = 2;
 8358«letˇ» foo = 2;
 8359let «fooˇ» = 2;
 8360let foo = 2;
 8361let foo = «2ˇ»;"#,
 8362    );
 8363
 8364    // Test last selection direction should be preserved
 8365    cx.set_state(
 8366        r#"let foo = 2;
 8367let foo = 2;
 8368let «fooˇ» = 2;
 8369let «ˇfoo» = 2;
 8370let foo = 2;"#,
 8371    );
 8372
 8373    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8374        .unwrap();
 8375    cx.assert_editor_state(
 8376        r#"let foo = 2;
 8377let foo = 2;
 8378let «fooˇ» = 2;
 8379let «ˇfoo» = 2;
 8380let «ˇfoo» = 2;"#,
 8381    );
 8382}
 8383
 8384#[gpui::test]
 8385async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8386    init_test(cx, |_| {});
 8387
 8388    let mut cx =
 8389        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8390
 8391    cx.assert_editor_state(indoc! {"
 8392        ˇbbb
 8393        ccc
 8394
 8395        bbb
 8396        ccc
 8397        "});
 8398    cx.dispatch_action(SelectPrevious::default());
 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}
 8415
 8416#[gpui::test]
 8417async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8418    init_test(cx, |_| {});
 8419
 8420    let mut cx = EditorTestContext::new(cx).await;
 8421    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8422
 8423    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8424        .unwrap();
 8425    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8426
 8427    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8428        .unwrap();
 8429    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8430
 8431    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8432    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8433
 8434    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8435    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8436
 8437    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8438        .unwrap();
 8439    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 8440
 8441    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8442        .unwrap();
 8443    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8444}
 8445
 8446#[gpui::test]
 8447async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8448    init_test(cx, |_| {});
 8449
 8450    let mut cx = EditorTestContext::new(cx).await;
 8451    cx.set_state("");
 8452
 8453    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8454        .unwrap();
 8455    cx.assert_editor_state("«aˇ»");
 8456    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8457        .unwrap();
 8458    cx.assert_editor_state("«aˇ»");
 8459}
 8460
 8461#[gpui::test]
 8462async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8463    init_test(cx, |_| {});
 8464
 8465    let mut cx = EditorTestContext::new(cx).await;
 8466    cx.set_state(
 8467        r#"let foo = 2;
 8468lˇet foo = 2;
 8469let fooˇ = 2;
 8470let foo = 2;
 8471let foo = ˇ2;"#,
 8472    );
 8473
 8474    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8475        .unwrap();
 8476    cx.assert_editor_state(
 8477        r#"let foo = 2;
 8478«letˇ» foo = 2;
 8479let «fooˇ» = 2;
 8480let foo = 2;
 8481let foo = «2ˇ»;"#,
 8482    );
 8483
 8484    // noop for multiple selections with different contents
 8485    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8486        .unwrap();
 8487    cx.assert_editor_state(
 8488        r#"let foo = 2;
 8489«letˇ» foo = 2;
 8490let «fooˇ» = 2;
 8491let foo = 2;
 8492let foo = «2ˇ»;"#,
 8493    );
 8494}
 8495
 8496#[gpui::test]
 8497async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 8498    init_test(cx, |_| {});
 8499
 8500    let mut cx = EditorTestContext::new(cx).await;
 8501    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8502
 8503    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8504        .unwrap();
 8505    // selection direction is preserved
 8506    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8507
 8508    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8509        .unwrap();
 8510    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8511
 8512    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8513    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8514
 8515    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8516    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8517
 8518    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8519        .unwrap();
 8520    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 8521
 8522    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8523        .unwrap();
 8524    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 8525}
 8526
 8527#[gpui::test]
 8528async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 8529    init_test(cx, |_| {});
 8530
 8531    let language = Arc::new(Language::new(
 8532        LanguageConfig::default(),
 8533        Some(tree_sitter_rust::LANGUAGE.into()),
 8534    ));
 8535
 8536    let text = r#"
 8537        use mod1::mod2::{mod3, mod4};
 8538
 8539        fn fn_1(param1: bool, param2: &str) {
 8540            let var1 = "text";
 8541        }
 8542    "#
 8543    .unindent();
 8544
 8545    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8546    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8547    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8548
 8549    editor
 8550        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8551        .await;
 8552
 8553    editor.update_in(cx, |editor, window, cx| {
 8554        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8555            s.select_display_ranges([
 8556                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 8557                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 8558                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 8559            ]);
 8560        });
 8561        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8562    });
 8563    editor.update(cx, |editor, cx| {
 8564        assert_text_with_selections(
 8565            editor,
 8566            indoc! {r#"
 8567                use mod1::mod2::{mod3, «mod4ˇ»};
 8568
 8569                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8570                    let var1 = "«ˇtext»";
 8571                }
 8572            "#},
 8573            cx,
 8574        );
 8575    });
 8576
 8577    editor.update_in(cx, |editor, window, cx| {
 8578        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8579    });
 8580    editor.update(cx, |editor, cx| {
 8581        assert_text_with_selections(
 8582            editor,
 8583            indoc! {r#"
 8584                use mod1::mod2::«{mod3, mod4}ˇ»;
 8585
 8586                «ˇfn fn_1(param1: bool, param2: &str) {
 8587                    let var1 = "text";
 8588 8589            "#},
 8590            cx,
 8591        );
 8592    });
 8593
 8594    editor.update_in(cx, |editor, window, cx| {
 8595        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8596    });
 8597    assert_eq!(
 8598        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8599        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8600    );
 8601
 8602    // Trying to expand the selected syntax node one more time has no effect.
 8603    editor.update_in(cx, |editor, window, cx| {
 8604        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8605    });
 8606    assert_eq!(
 8607        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8608        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8609    );
 8610
 8611    editor.update_in(cx, |editor, window, cx| {
 8612        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8613    });
 8614    editor.update(cx, |editor, cx| {
 8615        assert_text_with_selections(
 8616            editor,
 8617            indoc! {r#"
 8618                use mod1::mod2::«{mod3, mod4}ˇ»;
 8619
 8620                «ˇfn fn_1(param1: bool, param2: &str) {
 8621                    let var1 = "text";
 8622 8623            "#},
 8624            cx,
 8625        );
 8626    });
 8627
 8628    editor.update_in(cx, |editor, window, cx| {
 8629        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8630    });
 8631    editor.update(cx, |editor, cx| {
 8632        assert_text_with_selections(
 8633            editor,
 8634            indoc! {r#"
 8635                use mod1::mod2::{mod3, «mod4ˇ»};
 8636
 8637                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8638                    let var1 = "«ˇtext»";
 8639                }
 8640            "#},
 8641            cx,
 8642        );
 8643    });
 8644
 8645    editor.update_in(cx, |editor, window, cx| {
 8646        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8647    });
 8648    editor.update(cx, |editor, cx| {
 8649        assert_text_with_selections(
 8650            editor,
 8651            indoc! {r#"
 8652                use mod1::mod2::{mod3, moˇd4};
 8653
 8654                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8655                    let var1 = "teˇxt";
 8656                }
 8657            "#},
 8658            cx,
 8659        );
 8660    });
 8661
 8662    // Trying to shrink the selected syntax node one more time has no effect.
 8663    editor.update_in(cx, |editor, window, cx| {
 8664        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8665    });
 8666    editor.update_in(cx, |editor, _, cx| {
 8667        assert_text_with_selections(
 8668            editor,
 8669            indoc! {r#"
 8670                use mod1::mod2::{mod3, moˇd4};
 8671
 8672                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8673                    let var1 = "teˇxt";
 8674                }
 8675            "#},
 8676            cx,
 8677        );
 8678    });
 8679
 8680    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 8681    // a fold.
 8682    editor.update_in(cx, |editor, window, cx| {
 8683        editor.fold_creases(
 8684            vec![
 8685                Crease::simple(
 8686                    Point::new(0, 21)..Point::new(0, 24),
 8687                    FoldPlaceholder::test(),
 8688                ),
 8689                Crease::simple(
 8690                    Point::new(3, 20)..Point::new(3, 22),
 8691                    FoldPlaceholder::test(),
 8692                ),
 8693            ],
 8694            true,
 8695            window,
 8696            cx,
 8697        );
 8698        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8699    });
 8700    editor.update(cx, |editor, cx| {
 8701        assert_text_with_selections(
 8702            editor,
 8703            indoc! {r#"
 8704                use mod1::mod2::«{mod3, mod4}ˇ»;
 8705
 8706                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8707                    let var1 = "«ˇtext»";
 8708                }
 8709            "#},
 8710            cx,
 8711        );
 8712    });
 8713}
 8714
 8715#[gpui::test]
 8716async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 8717    init_test(cx, |_| {});
 8718
 8719    let language = Arc::new(Language::new(
 8720        LanguageConfig::default(),
 8721        Some(tree_sitter_rust::LANGUAGE.into()),
 8722    ));
 8723
 8724    let text = "let a = 2;";
 8725
 8726    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8727    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8728    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8729
 8730    editor
 8731        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8732        .await;
 8733
 8734    // Test case 1: Cursor at end of word
 8735    editor.update_in(cx, |editor, window, cx| {
 8736        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8737            s.select_display_ranges([
 8738                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 8739            ]);
 8740        });
 8741    });
 8742    editor.update(cx, |editor, cx| {
 8743        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 8744    });
 8745    editor.update_in(cx, |editor, window, cx| {
 8746        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8747    });
 8748    editor.update(cx, |editor, cx| {
 8749        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 8750    });
 8751    editor.update_in(cx, |editor, window, cx| {
 8752        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8753    });
 8754    editor.update(cx, |editor, cx| {
 8755        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 8756    });
 8757
 8758    // Test case 2: Cursor at end of statement
 8759    editor.update_in(cx, |editor, window, cx| {
 8760        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8761            s.select_display_ranges([
 8762                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 8763            ]);
 8764        });
 8765    });
 8766    editor.update(cx, |editor, cx| {
 8767        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 8768    });
 8769    editor.update_in(cx, |editor, window, cx| {
 8770        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8771    });
 8772    editor.update(cx, |editor, cx| {
 8773        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 8774    });
 8775}
 8776
 8777#[gpui::test]
 8778async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 8779    init_test(cx, |_| {});
 8780
 8781    let language = Arc::new(Language::new(
 8782        LanguageConfig {
 8783            name: "JavaScript".into(),
 8784            ..Default::default()
 8785        },
 8786        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8787    ));
 8788
 8789    let text = r#"
 8790        let a = {
 8791            key: "value",
 8792        };
 8793    "#
 8794    .unindent();
 8795
 8796    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8797    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8798    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8799
 8800    editor
 8801        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8802        .await;
 8803
 8804    // Test case 1: Cursor after '{'
 8805    editor.update_in(cx, |editor, window, cx| {
 8806        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8807            s.select_display_ranges([
 8808                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 8809            ]);
 8810        });
 8811    });
 8812    editor.update(cx, |editor, cx| {
 8813        assert_text_with_selections(
 8814            editor,
 8815            indoc! {r#"
 8816                let a = {ˇ
 8817                    key: "value",
 8818                };
 8819            "#},
 8820            cx,
 8821        );
 8822    });
 8823    editor.update_in(cx, |editor, window, cx| {
 8824        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8825    });
 8826    editor.update(cx, |editor, cx| {
 8827        assert_text_with_selections(
 8828            editor,
 8829            indoc! {r#"
 8830                let a = «ˇ{
 8831                    key: "value",
 8832                }»;
 8833            "#},
 8834            cx,
 8835        );
 8836    });
 8837
 8838    // Test case 2: Cursor after ':'
 8839    editor.update_in(cx, |editor, window, cx| {
 8840        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8841            s.select_display_ranges([
 8842                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 8843            ]);
 8844        });
 8845    });
 8846    editor.update(cx, |editor, cx| {
 8847        assert_text_with_selections(
 8848            editor,
 8849            indoc! {r#"
 8850                let a = {
 8851                    key:ˇ "value",
 8852                };
 8853            "#},
 8854            cx,
 8855        );
 8856    });
 8857    editor.update_in(cx, |editor, window, cx| {
 8858        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8859    });
 8860    editor.update(cx, |editor, cx| {
 8861        assert_text_with_selections(
 8862            editor,
 8863            indoc! {r#"
 8864                let a = {
 8865                    «ˇkey: "value"»,
 8866                };
 8867            "#},
 8868            cx,
 8869        );
 8870    });
 8871    editor.update_in(cx, |editor, window, cx| {
 8872        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8873    });
 8874    editor.update(cx, |editor, cx| {
 8875        assert_text_with_selections(
 8876            editor,
 8877            indoc! {r#"
 8878                let a = «ˇ{
 8879                    key: "value",
 8880                }»;
 8881            "#},
 8882            cx,
 8883        );
 8884    });
 8885
 8886    // Test case 3: Cursor after ','
 8887    editor.update_in(cx, |editor, window, cx| {
 8888        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8889            s.select_display_ranges([
 8890                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 8891            ]);
 8892        });
 8893    });
 8894    editor.update(cx, |editor, cx| {
 8895        assert_text_with_selections(
 8896            editor,
 8897            indoc! {r#"
 8898                let a = {
 8899                    key: "value",ˇ
 8900                };
 8901            "#},
 8902            cx,
 8903        );
 8904    });
 8905    editor.update_in(cx, |editor, window, cx| {
 8906        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8907    });
 8908    editor.update(cx, |editor, cx| {
 8909        assert_text_with_selections(
 8910            editor,
 8911            indoc! {r#"
 8912                let a = «ˇ{
 8913                    key: "value",
 8914                }»;
 8915            "#},
 8916            cx,
 8917        );
 8918    });
 8919
 8920    // Test case 4: Cursor after ';'
 8921    editor.update_in(cx, |editor, window, cx| {
 8922        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8923            s.select_display_ranges([
 8924                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 8925            ]);
 8926        });
 8927    });
 8928    editor.update(cx, |editor, cx| {
 8929        assert_text_with_selections(
 8930            editor,
 8931            indoc! {r#"
 8932                let a = {
 8933                    key: "value",
 8934                };ˇ
 8935            "#},
 8936            cx,
 8937        );
 8938    });
 8939    editor.update_in(cx, |editor, window, cx| {
 8940        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8941    });
 8942    editor.update(cx, |editor, cx| {
 8943        assert_text_with_selections(
 8944            editor,
 8945            indoc! {r#"
 8946                «ˇlet a = {
 8947                    key: "value",
 8948                };
 8949                »"#},
 8950            cx,
 8951        );
 8952    });
 8953}
 8954
 8955#[gpui::test]
 8956async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 8957    init_test(cx, |_| {});
 8958
 8959    let language = Arc::new(Language::new(
 8960        LanguageConfig::default(),
 8961        Some(tree_sitter_rust::LANGUAGE.into()),
 8962    ));
 8963
 8964    let text = r#"
 8965        use mod1::mod2::{mod3, mod4};
 8966
 8967        fn fn_1(param1: bool, param2: &str) {
 8968            let var1 = "hello world";
 8969        }
 8970    "#
 8971    .unindent();
 8972
 8973    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8974    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8975    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8976
 8977    editor
 8978        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8979        .await;
 8980
 8981    // Test 1: Cursor on a letter of a string word
 8982    editor.update_in(cx, |editor, window, cx| {
 8983        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8984            s.select_display_ranges([
 8985                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 8986            ]);
 8987        });
 8988    });
 8989    editor.update_in(cx, |editor, window, cx| {
 8990        assert_text_with_selections(
 8991            editor,
 8992            indoc! {r#"
 8993                use mod1::mod2::{mod3, mod4};
 8994
 8995                fn fn_1(param1: bool, param2: &str) {
 8996                    let var1 = "hˇello world";
 8997                }
 8998            "#},
 8999            cx,
 9000        );
 9001        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9002        assert_text_with_selections(
 9003            editor,
 9004            indoc! {r#"
 9005                use mod1::mod2::{mod3, mod4};
 9006
 9007                fn fn_1(param1: bool, param2: &str) {
 9008                    let var1 = "«ˇhello» world";
 9009                }
 9010            "#},
 9011            cx,
 9012        );
 9013    });
 9014
 9015    // Test 2: Partial selection within a word
 9016    editor.update_in(cx, |editor, window, cx| {
 9017        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9018            s.select_display_ranges([
 9019                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9020            ]);
 9021        });
 9022    });
 9023    editor.update_in(cx, |editor, window, cx| {
 9024        assert_text_with_selections(
 9025            editor,
 9026            indoc! {r#"
 9027                use mod1::mod2::{mod3, mod4};
 9028
 9029                fn fn_1(param1: bool, param2: &str) {
 9030                    let var1 = "h«elˇ»lo world";
 9031                }
 9032            "#},
 9033            cx,
 9034        );
 9035        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9036        assert_text_with_selections(
 9037            editor,
 9038            indoc! {r#"
 9039                use mod1::mod2::{mod3, mod4};
 9040
 9041                fn fn_1(param1: bool, param2: &str) {
 9042                    let var1 = "«ˇhello» world";
 9043                }
 9044            "#},
 9045            cx,
 9046        );
 9047    });
 9048
 9049    // Test 3: Complete word already selected
 9050    editor.update_in(cx, |editor, window, cx| {
 9051        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9052            s.select_display_ranges([
 9053                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9054            ]);
 9055        });
 9056    });
 9057    editor.update_in(cx, |editor, window, cx| {
 9058        assert_text_with_selections(
 9059            editor,
 9060            indoc! {r#"
 9061                use mod1::mod2::{mod3, mod4};
 9062
 9063                fn fn_1(param1: bool, param2: &str) {
 9064                    let var1 = "«helloˇ» world";
 9065                }
 9066            "#},
 9067            cx,
 9068        );
 9069        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9070        assert_text_with_selections(
 9071            editor,
 9072            indoc! {r#"
 9073                use mod1::mod2::{mod3, mod4};
 9074
 9075                fn fn_1(param1: bool, param2: &str) {
 9076                    let var1 = "«hello worldˇ»";
 9077                }
 9078            "#},
 9079            cx,
 9080        );
 9081    });
 9082
 9083    // Test 4: Selection spanning across words
 9084    editor.update_in(cx, |editor, window, cx| {
 9085        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9086            s.select_display_ranges([
 9087                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9088            ]);
 9089        });
 9090    });
 9091    editor.update_in(cx, |editor, window, cx| {
 9092        assert_text_with_selections(
 9093            editor,
 9094            indoc! {r#"
 9095                use mod1::mod2::{mod3, mod4};
 9096
 9097                fn fn_1(param1: bool, param2: &str) {
 9098                    let var1 = "hel«lo woˇ»rld";
 9099                }
 9100            "#},
 9101            cx,
 9102        );
 9103        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9104        assert_text_with_selections(
 9105            editor,
 9106            indoc! {r#"
 9107                use mod1::mod2::{mod3, mod4};
 9108
 9109                fn fn_1(param1: bool, param2: &str) {
 9110                    let var1 = "«ˇhello world»";
 9111                }
 9112            "#},
 9113            cx,
 9114        );
 9115    });
 9116
 9117    // Test 5: Expansion beyond string
 9118    editor.update_in(cx, |editor, window, cx| {
 9119        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9120        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9121        assert_text_with_selections(
 9122            editor,
 9123            indoc! {r#"
 9124                use mod1::mod2::{mod3, mod4};
 9125
 9126                fn fn_1(param1: bool, param2: &str) {
 9127                    «ˇlet var1 = "hello world";»
 9128                }
 9129            "#},
 9130            cx,
 9131        );
 9132    });
 9133}
 9134
 9135#[gpui::test]
 9136async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9137    init_test(cx, |_| {});
 9138
 9139    let mut cx = EditorTestContext::new(cx).await;
 9140
 9141    let language = Arc::new(Language::new(
 9142        LanguageConfig::default(),
 9143        Some(tree_sitter_rust::LANGUAGE.into()),
 9144    ));
 9145
 9146    cx.update_buffer(|buffer, cx| {
 9147        buffer.set_language(Some(language), cx);
 9148    });
 9149
 9150    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9151    cx.update_editor(|editor, window, cx| {
 9152        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9153    });
 9154
 9155    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9156}
 9157
 9158#[gpui::test]
 9159async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9160    init_test(cx, |_| {});
 9161
 9162    let base_text = r#"
 9163        impl A {
 9164            // this is an uncommitted comment
 9165
 9166            fn b() {
 9167                c();
 9168            }
 9169
 9170            // this is another uncommitted comment
 9171
 9172            fn d() {
 9173                // e
 9174                // f
 9175            }
 9176        }
 9177
 9178        fn g() {
 9179            // h
 9180        }
 9181    "#
 9182    .unindent();
 9183
 9184    let text = r#"
 9185        ˇimpl A {
 9186
 9187            fn b() {
 9188                c();
 9189            }
 9190
 9191            fn d() {
 9192                // e
 9193                // f
 9194            }
 9195        }
 9196
 9197        fn g() {
 9198            // h
 9199        }
 9200    "#
 9201    .unindent();
 9202
 9203    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9204    cx.set_state(&text);
 9205    cx.set_head_text(&base_text);
 9206    cx.update_editor(|editor, window, cx| {
 9207        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9208    });
 9209
 9210    cx.assert_state_with_diff(
 9211        "
 9212        ˇimpl A {
 9213      -     // this is an uncommitted comment
 9214
 9215            fn b() {
 9216                c();
 9217            }
 9218
 9219      -     // this is another uncommitted comment
 9220      -
 9221            fn d() {
 9222                // e
 9223                // f
 9224            }
 9225        }
 9226
 9227        fn g() {
 9228            // h
 9229        }
 9230    "
 9231        .unindent(),
 9232    );
 9233
 9234    let expected_display_text = "
 9235        impl A {
 9236            // this is an uncommitted comment
 9237
 9238            fn b() {
 9239 9240            }
 9241
 9242            // this is another uncommitted comment
 9243
 9244            fn d() {
 9245 9246            }
 9247        }
 9248
 9249        fn g() {
 9250 9251        }
 9252        "
 9253    .unindent();
 9254
 9255    cx.update_editor(|editor, window, cx| {
 9256        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9257        assert_eq!(editor.display_text(cx), expected_display_text);
 9258    });
 9259}
 9260
 9261#[gpui::test]
 9262async fn test_autoindent(cx: &mut TestAppContext) {
 9263    init_test(cx, |_| {});
 9264
 9265    let language = Arc::new(
 9266        Language::new(
 9267            LanguageConfig {
 9268                brackets: BracketPairConfig {
 9269                    pairs: vec![
 9270                        BracketPair {
 9271                            start: "{".to_string(),
 9272                            end: "}".to_string(),
 9273                            close: false,
 9274                            surround: false,
 9275                            newline: true,
 9276                        },
 9277                        BracketPair {
 9278                            start: "(".to_string(),
 9279                            end: ")".to_string(),
 9280                            close: false,
 9281                            surround: false,
 9282                            newline: true,
 9283                        },
 9284                    ],
 9285                    ..Default::default()
 9286                },
 9287                ..Default::default()
 9288            },
 9289            Some(tree_sitter_rust::LANGUAGE.into()),
 9290        )
 9291        .with_indents_query(
 9292            r#"
 9293                (_ "(" ")" @end) @indent
 9294                (_ "{" "}" @end) @indent
 9295            "#,
 9296        )
 9297        .unwrap(),
 9298    );
 9299
 9300    let text = "fn a() {}";
 9301
 9302    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9303    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9304    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9305    editor
 9306        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9307        .await;
 9308
 9309    editor.update_in(cx, |editor, window, cx| {
 9310        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9311            s.select_ranges([5..5, 8..8, 9..9])
 9312        });
 9313        editor.newline(&Newline, window, cx);
 9314        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9315        assert_eq!(
 9316            editor.selections.ranges(cx),
 9317            &[
 9318                Point::new(1, 4)..Point::new(1, 4),
 9319                Point::new(3, 4)..Point::new(3, 4),
 9320                Point::new(5, 0)..Point::new(5, 0)
 9321            ]
 9322        );
 9323    });
 9324}
 9325
 9326#[gpui::test]
 9327async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9328    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9329
 9330    let language = Arc::new(
 9331        Language::new(
 9332            LanguageConfig {
 9333                brackets: BracketPairConfig {
 9334                    pairs: vec![
 9335                        BracketPair {
 9336                            start: "{".to_string(),
 9337                            end: "}".to_string(),
 9338                            close: false,
 9339                            surround: false,
 9340                            newline: true,
 9341                        },
 9342                        BracketPair {
 9343                            start: "(".to_string(),
 9344                            end: ")".to_string(),
 9345                            close: false,
 9346                            surround: false,
 9347                            newline: true,
 9348                        },
 9349                    ],
 9350                    ..Default::default()
 9351                },
 9352                ..Default::default()
 9353            },
 9354            Some(tree_sitter_rust::LANGUAGE.into()),
 9355        )
 9356        .with_indents_query(
 9357            r#"
 9358                (_ "(" ")" @end) @indent
 9359                (_ "{" "}" @end) @indent
 9360            "#,
 9361        )
 9362        .unwrap(),
 9363    );
 9364
 9365    let text = "fn a() {}";
 9366
 9367    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9368    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9369    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9370    editor
 9371        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9372        .await;
 9373
 9374    editor.update_in(cx, |editor, window, cx| {
 9375        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9376            s.select_ranges([5..5, 8..8, 9..9])
 9377        });
 9378        editor.newline(&Newline, window, cx);
 9379        assert_eq!(
 9380            editor.text(cx),
 9381            indoc!(
 9382                "
 9383                fn a(
 9384
 9385                ) {
 9386
 9387                }
 9388                "
 9389            )
 9390        );
 9391        assert_eq!(
 9392            editor.selections.ranges(cx),
 9393            &[
 9394                Point::new(1, 0)..Point::new(1, 0),
 9395                Point::new(3, 0)..Point::new(3, 0),
 9396                Point::new(5, 0)..Point::new(5, 0)
 9397            ]
 9398        );
 9399    });
 9400}
 9401
 9402#[gpui::test]
 9403async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 9404    init_test(cx, |settings| {
 9405        settings.defaults.auto_indent = Some(true);
 9406        settings.languages.0.insert(
 9407            "python".into(),
 9408            LanguageSettingsContent {
 9409                auto_indent: Some(false),
 9410                ..Default::default()
 9411            },
 9412        );
 9413    });
 9414
 9415    let mut cx = EditorTestContext::new(cx).await;
 9416
 9417    let injected_language = Arc::new(
 9418        Language::new(
 9419            LanguageConfig {
 9420                brackets: BracketPairConfig {
 9421                    pairs: vec![
 9422                        BracketPair {
 9423                            start: "{".to_string(),
 9424                            end: "}".to_string(),
 9425                            close: false,
 9426                            surround: false,
 9427                            newline: true,
 9428                        },
 9429                        BracketPair {
 9430                            start: "(".to_string(),
 9431                            end: ")".to_string(),
 9432                            close: true,
 9433                            surround: false,
 9434                            newline: true,
 9435                        },
 9436                    ],
 9437                    ..Default::default()
 9438                },
 9439                name: "python".into(),
 9440                ..Default::default()
 9441            },
 9442            Some(tree_sitter_python::LANGUAGE.into()),
 9443        )
 9444        .with_indents_query(
 9445            r#"
 9446                (_ "(" ")" @end) @indent
 9447                (_ "{" "}" @end) @indent
 9448            "#,
 9449        )
 9450        .unwrap(),
 9451    );
 9452
 9453    let language = Arc::new(
 9454        Language::new(
 9455            LanguageConfig {
 9456                brackets: BracketPairConfig {
 9457                    pairs: vec![
 9458                        BracketPair {
 9459                            start: "{".to_string(),
 9460                            end: "}".to_string(),
 9461                            close: false,
 9462                            surround: false,
 9463                            newline: true,
 9464                        },
 9465                        BracketPair {
 9466                            start: "(".to_string(),
 9467                            end: ")".to_string(),
 9468                            close: true,
 9469                            surround: false,
 9470                            newline: true,
 9471                        },
 9472                    ],
 9473                    ..Default::default()
 9474                },
 9475                name: LanguageName::new("rust"),
 9476                ..Default::default()
 9477            },
 9478            Some(tree_sitter_rust::LANGUAGE.into()),
 9479        )
 9480        .with_indents_query(
 9481            r#"
 9482                (_ "(" ")" @end) @indent
 9483                (_ "{" "}" @end) @indent
 9484            "#,
 9485        )
 9486        .unwrap()
 9487        .with_injection_query(
 9488            r#"
 9489            (macro_invocation
 9490                macro: (identifier) @_macro_name
 9491                (token_tree) @injection.content
 9492                (#set! injection.language "python"))
 9493           "#,
 9494        )
 9495        .unwrap(),
 9496    );
 9497
 9498    cx.language_registry().add(injected_language);
 9499    cx.language_registry().add(language.clone());
 9500
 9501    cx.update_buffer(|buffer, cx| {
 9502        buffer.set_language(Some(language), cx);
 9503    });
 9504
 9505    cx.set_state(r#"struct A {ˇ}"#);
 9506
 9507    cx.update_editor(|editor, window, cx| {
 9508        editor.newline(&Default::default(), window, cx);
 9509    });
 9510
 9511    cx.assert_editor_state(indoc!(
 9512        "struct A {
 9513            ˇ
 9514        }"
 9515    ));
 9516
 9517    cx.set_state(r#"select_biased!(ˇ)"#);
 9518
 9519    cx.update_editor(|editor, window, cx| {
 9520        editor.newline(&Default::default(), window, cx);
 9521        editor.handle_input("def ", window, cx);
 9522        editor.handle_input("(", window, cx);
 9523        editor.newline(&Default::default(), window, cx);
 9524        editor.handle_input("a", window, cx);
 9525    });
 9526
 9527    cx.assert_editor_state(indoc!(
 9528        "select_biased!(
 9529        def (
 9530 9531        )
 9532        )"
 9533    ));
 9534}
 9535
 9536#[gpui::test]
 9537async fn test_autoindent_selections(cx: &mut TestAppContext) {
 9538    init_test(cx, |_| {});
 9539
 9540    {
 9541        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9542        cx.set_state(indoc! {"
 9543            impl A {
 9544
 9545                fn b() {}
 9546
 9547            «fn c() {
 9548
 9549            }ˇ»
 9550            }
 9551        "});
 9552
 9553        cx.update_editor(|editor, window, cx| {
 9554            editor.autoindent(&Default::default(), window, cx);
 9555        });
 9556
 9557        cx.assert_editor_state(indoc! {"
 9558            impl A {
 9559
 9560                fn b() {}
 9561
 9562                «fn c() {
 9563
 9564                }ˇ»
 9565            }
 9566        "});
 9567    }
 9568
 9569    {
 9570        let mut cx = EditorTestContext::new_multibuffer(
 9571            cx,
 9572            [indoc! { "
 9573                impl A {
 9574                «
 9575                // a
 9576                fn b(){}
 9577                »
 9578                «
 9579                    }
 9580                    fn c(){}
 9581                »
 9582            "}],
 9583        );
 9584
 9585        let buffer = cx.update_editor(|editor, _, cx| {
 9586            let buffer = editor.buffer().update(cx, |buffer, _| {
 9587                buffer.all_buffers().iter().next().unwrap().clone()
 9588            });
 9589            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 9590            buffer
 9591        });
 9592
 9593        cx.run_until_parked();
 9594        cx.update_editor(|editor, window, cx| {
 9595            editor.select_all(&Default::default(), window, cx);
 9596            editor.autoindent(&Default::default(), window, cx)
 9597        });
 9598        cx.run_until_parked();
 9599
 9600        cx.update(|_, cx| {
 9601            assert_eq!(
 9602                buffer.read(cx).text(),
 9603                indoc! { "
 9604                    impl A {
 9605
 9606                        // a
 9607                        fn b(){}
 9608
 9609
 9610                    }
 9611                    fn c(){}
 9612
 9613                " }
 9614            )
 9615        });
 9616    }
 9617}
 9618
 9619#[gpui::test]
 9620async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 9621    init_test(cx, |_| {});
 9622
 9623    let mut cx = EditorTestContext::new(cx).await;
 9624
 9625    let language = Arc::new(Language::new(
 9626        LanguageConfig {
 9627            brackets: BracketPairConfig {
 9628                pairs: vec![
 9629                    BracketPair {
 9630                        start: "{".to_string(),
 9631                        end: "}".to_string(),
 9632                        close: true,
 9633                        surround: true,
 9634                        newline: true,
 9635                    },
 9636                    BracketPair {
 9637                        start: "(".to_string(),
 9638                        end: ")".to_string(),
 9639                        close: true,
 9640                        surround: true,
 9641                        newline: true,
 9642                    },
 9643                    BracketPair {
 9644                        start: "/*".to_string(),
 9645                        end: " */".to_string(),
 9646                        close: true,
 9647                        surround: true,
 9648                        newline: true,
 9649                    },
 9650                    BracketPair {
 9651                        start: "[".to_string(),
 9652                        end: "]".to_string(),
 9653                        close: false,
 9654                        surround: false,
 9655                        newline: true,
 9656                    },
 9657                    BracketPair {
 9658                        start: "\"".to_string(),
 9659                        end: "\"".to_string(),
 9660                        close: true,
 9661                        surround: true,
 9662                        newline: false,
 9663                    },
 9664                    BracketPair {
 9665                        start: "<".to_string(),
 9666                        end: ">".to_string(),
 9667                        close: false,
 9668                        surround: true,
 9669                        newline: true,
 9670                    },
 9671                ],
 9672                ..Default::default()
 9673            },
 9674            autoclose_before: "})]".to_string(),
 9675            ..Default::default()
 9676        },
 9677        Some(tree_sitter_rust::LANGUAGE.into()),
 9678    ));
 9679
 9680    cx.language_registry().add(language.clone());
 9681    cx.update_buffer(|buffer, cx| {
 9682        buffer.set_language(Some(language), cx);
 9683    });
 9684
 9685    cx.set_state(
 9686        &r#"
 9687            🏀ˇ
 9688            εˇ
 9689            ❤️ˇ
 9690        "#
 9691        .unindent(),
 9692    );
 9693
 9694    // autoclose multiple nested brackets at multiple cursors
 9695    cx.update_editor(|editor, window, cx| {
 9696        editor.handle_input("{", window, cx);
 9697        editor.handle_input("{", window, cx);
 9698        editor.handle_input("{", window, cx);
 9699    });
 9700    cx.assert_editor_state(
 9701        &"
 9702            🏀{{{ˇ}}}
 9703            ε{{{ˇ}}}
 9704            ❤️{{{ˇ}}}
 9705        "
 9706        .unindent(),
 9707    );
 9708
 9709    // insert a different closing bracket
 9710    cx.update_editor(|editor, window, cx| {
 9711        editor.handle_input(")", window, cx);
 9712    });
 9713    cx.assert_editor_state(
 9714        &"
 9715            🏀{{{)ˇ}}}
 9716            ε{{{)ˇ}}}
 9717            ❤️{{{)ˇ}}}
 9718        "
 9719        .unindent(),
 9720    );
 9721
 9722    // skip over the auto-closed brackets when typing a closing bracket
 9723    cx.update_editor(|editor, window, cx| {
 9724        editor.move_right(&MoveRight, window, cx);
 9725        editor.handle_input("}", window, cx);
 9726        editor.handle_input("}", window, cx);
 9727        editor.handle_input("}", window, cx);
 9728    });
 9729    cx.assert_editor_state(
 9730        &"
 9731            🏀{{{)}}}}ˇ
 9732            ε{{{)}}}}ˇ
 9733            ❤️{{{)}}}}ˇ
 9734        "
 9735        .unindent(),
 9736    );
 9737
 9738    // autoclose multi-character pairs
 9739    cx.set_state(
 9740        &"
 9741            ˇ
 9742            ˇ
 9743        "
 9744        .unindent(),
 9745    );
 9746    cx.update_editor(|editor, window, cx| {
 9747        editor.handle_input("/", window, cx);
 9748        editor.handle_input("*", window, cx);
 9749    });
 9750    cx.assert_editor_state(
 9751        &"
 9752            /*ˇ */
 9753            /*ˇ */
 9754        "
 9755        .unindent(),
 9756    );
 9757
 9758    // one cursor autocloses a multi-character pair, one cursor
 9759    // does not autoclose.
 9760    cx.set_state(
 9761        &"
 9762 9763            ˇ
 9764        "
 9765        .unindent(),
 9766    );
 9767    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 9768    cx.assert_editor_state(
 9769        &"
 9770            /*ˇ */
 9771 9772        "
 9773        .unindent(),
 9774    );
 9775
 9776    // Don't autoclose if the next character isn't whitespace and isn't
 9777    // listed in the language's "autoclose_before" section.
 9778    cx.set_state("ˇa b");
 9779    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9780    cx.assert_editor_state("{ˇa b");
 9781
 9782    // Don't autoclose if `close` is false for the bracket pair
 9783    cx.set_state("ˇ");
 9784    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 9785    cx.assert_editor_state("");
 9786
 9787    // Surround with brackets if text is selected
 9788    cx.set_state("«aˇ» b");
 9789    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9790    cx.assert_editor_state("{«aˇ»} b");
 9791
 9792    // Autoclose when not immediately after a word character
 9793    cx.set_state("a ˇ");
 9794    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9795    cx.assert_editor_state("a \"ˇ\"");
 9796
 9797    // Autoclose pair where the start and end characters are the same
 9798    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9799    cx.assert_editor_state("a \"\"ˇ");
 9800
 9801    // Don't autoclose when immediately after a word character
 9802    cx.set_state("");
 9803    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9804    cx.assert_editor_state("a\"ˇ");
 9805
 9806    // Do autoclose when after a non-word character
 9807    cx.set_state("");
 9808    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9809    cx.assert_editor_state("{\"ˇ\"");
 9810
 9811    // Non identical pairs autoclose regardless of preceding character
 9812    cx.set_state("");
 9813    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9814    cx.assert_editor_state("a{ˇ}");
 9815
 9816    // Don't autoclose pair if autoclose is disabled
 9817    cx.set_state("ˇ");
 9818    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9819    cx.assert_editor_state("");
 9820
 9821    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 9822    cx.set_state("«aˇ» b");
 9823    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9824    cx.assert_editor_state("<«aˇ»> b");
 9825}
 9826
 9827#[gpui::test]
 9828async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 9829    init_test(cx, |settings| {
 9830        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9831    });
 9832
 9833    let mut cx = EditorTestContext::new(cx).await;
 9834
 9835    let language = Arc::new(Language::new(
 9836        LanguageConfig {
 9837            brackets: BracketPairConfig {
 9838                pairs: vec![
 9839                    BracketPair {
 9840                        start: "{".to_string(),
 9841                        end: "}".to_string(),
 9842                        close: true,
 9843                        surround: true,
 9844                        newline: true,
 9845                    },
 9846                    BracketPair {
 9847                        start: "(".to_string(),
 9848                        end: ")".to_string(),
 9849                        close: true,
 9850                        surround: true,
 9851                        newline: true,
 9852                    },
 9853                    BracketPair {
 9854                        start: "[".to_string(),
 9855                        end: "]".to_string(),
 9856                        close: false,
 9857                        surround: false,
 9858                        newline: true,
 9859                    },
 9860                ],
 9861                ..Default::default()
 9862            },
 9863            autoclose_before: "})]".to_string(),
 9864            ..Default::default()
 9865        },
 9866        Some(tree_sitter_rust::LANGUAGE.into()),
 9867    ));
 9868
 9869    cx.language_registry().add(language.clone());
 9870    cx.update_buffer(|buffer, cx| {
 9871        buffer.set_language(Some(language), cx);
 9872    });
 9873
 9874    cx.set_state(
 9875        &"
 9876            ˇ
 9877            ˇ
 9878            ˇ
 9879        "
 9880        .unindent(),
 9881    );
 9882
 9883    // ensure only matching closing brackets are skipped over
 9884    cx.update_editor(|editor, window, cx| {
 9885        editor.handle_input("}", window, cx);
 9886        editor.move_left(&MoveLeft, window, cx);
 9887        editor.handle_input(")", window, cx);
 9888        editor.move_left(&MoveLeft, window, cx);
 9889    });
 9890    cx.assert_editor_state(
 9891        &"
 9892            ˇ)}
 9893            ˇ)}
 9894            ˇ)}
 9895        "
 9896        .unindent(),
 9897    );
 9898
 9899    // skip-over closing brackets at multiple cursors
 9900    cx.update_editor(|editor, window, cx| {
 9901        editor.handle_input(")", window, cx);
 9902        editor.handle_input("}", window, cx);
 9903    });
 9904    cx.assert_editor_state(
 9905        &"
 9906            )}ˇ
 9907            )}ˇ
 9908            )}ˇ
 9909        "
 9910        .unindent(),
 9911    );
 9912
 9913    // ignore non-close brackets
 9914    cx.update_editor(|editor, window, cx| {
 9915        editor.handle_input("]", window, cx);
 9916        editor.move_left(&MoveLeft, window, cx);
 9917        editor.handle_input("]", window, cx);
 9918    });
 9919    cx.assert_editor_state(
 9920        &"
 9921            )}]ˇ]
 9922            )}]ˇ]
 9923            )}]ˇ]
 9924        "
 9925        .unindent(),
 9926    );
 9927}
 9928
 9929#[gpui::test]
 9930async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 9931    init_test(cx, |_| {});
 9932
 9933    let mut cx = EditorTestContext::new(cx).await;
 9934
 9935    let html_language = Arc::new(
 9936        Language::new(
 9937            LanguageConfig {
 9938                name: "HTML".into(),
 9939                brackets: BracketPairConfig {
 9940                    pairs: vec![
 9941                        BracketPair {
 9942                            start: "<".into(),
 9943                            end: ">".into(),
 9944                            close: true,
 9945                            ..Default::default()
 9946                        },
 9947                        BracketPair {
 9948                            start: "{".into(),
 9949                            end: "}".into(),
 9950                            close: true,
 9951                            ..Default::default()
 9952                        },
 9953                        BracketPair {
 9954                            start: "(".into(),
 9955                            end: ")".into(),
 9956                            close: true,
 9957                            ..Default::default()
 9958                        },
 9959                    ],
 9960                    ..Default::default()
 9961                },
 9962                autoclose_before: "})]>".into(),
 9963                ..Default::default()
 9964            },
 9965            Some(tree_sitter_html::LANGUAGE.into()),
 9966        )
 9967        .with_injection_query(
 9968            r#"
 9969            (script_element
 9970                (raw_text) @injection.content
 9971                (#set! injection.language "javascript"))
 9972            "#,
 9973        )
 9974        .unwrap(),
 9975    );
 9976
 9977    let javascript_language = Arc::new(Language::new(
 9978        LanguageConfig {
 9979            name: "JavaScript".into(),
 9980            brackets: BracketPairConfig {
 9981                pairs: vec![
 9982                    BracketPair {
 9983                        start: "/*".into(),
 9984                        end: " */".into(),
 9985                        close: true,
 9986                        ..Default::default()
 9987                    },
 9988                    BracketPair {
 9989                        start: "{".into(),
 9990                        end: "}".into(),
 9991                        close: true,
 9992                        ..Default::default()
 9993                    },
 9994                    BracketPair {
 9995                        start: "(".into(),
 9996                        end: ")".into(),
 9997                        close: true,
 9998                        ..Default::default()
 9999                    },
10000                ],
10001                ..Default::default()
10002            },
10003            autoclose_before: "})]>".into(),
10004            ..Default::default()
10005        },
10006        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10007    ));
10008
10009    cx.language_registry().add(html_language.clone());
10010    cx.language_registry().add(javascript_language);
10011    cx.executor().run_until_parked();
10012
10013    cx.update_buffer(|buffer, cx| {
10014        buffer.set_language(Some(html_language), cx);
10015    });
10016
10017    cx.set_state(
10018        &r#"
10019            <body>ˇ
10020                <script>
10021                    var x = 1;ˇ
10022                </script>
10023            </body>ˇ
10024        "#
10025        .unindent(),
10026    );
10027
10028    // Precondition: different languages are active at different locations.
10029    cx.update_editor(|editor, window, cx| {
10030        let snapshot = editor.snapshot(window, cx);
10031        let cursors = editor.selections.ranges::<usize>(cx);
10032        let languages = cursors
10033            .iter()
10034            .map(|c| snapshot.language_at(c.start).unwrap().name())
10035            .collect::<Vec<_>>();
10036        assert_eq!(
10037            languages,
10038            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10039        );
10040    });
10041
10042    // Angle brackets autoclose in HTML, but not JavaScript.
10043    cx.update_editor(|editor, window, cx| {
10044        editor.handle_input("<", window, cx);
10045        editor.handle_input("a", window, cx);
10046    });
10047    cx.assert_editor_state(
10048        &r#"
10049            <body><aˇ>
10050                <script>
10051                    var x = 1;<aˇ
10052                </script>
10053            </body><aˇ>
10054        "#
10055        .unindent(),
10056    );
10057
10058    // Curly braces and parens autoclose in both HTML and JavaScript.
10059    cx.update_editor(|editor, window, cx| {
10060        editor.handle_input(" b=", window, cx);
10061        editor.handle_input("{", window, cx);
10062        editor.handle_input("c", window, cx);
10063        editor.handle_input("(", window, cx);
10064    });
10065    cx.assert_editor_state(
10066        &r#"
10067            <body><a b={c(ˇ)}>
10068                <script>
10069                    var x = 1;<a b={c(ˇ)}
10070                </script>
10071            </body><a b={c(ˇ)}>
10072        "#
10073        .unindent(),
10074    );
10075
10076    // Brackets that were already autoclosed are skipped.
10077    cx.update_editor(|editor, window, cx| {
10078        editor.handle_input(")", window, cx);
10079        editor.handle_input("d", window, cx);
10080        editor.handle_input("}", window, cx);
10081    });
10082    cx.assert_editor_state(
10083        &r#"
10084            <body><a b={c()d}ˇ>
10085                <script>
10086                    var x = 1;<a b={c()d}ˇ
10087                </script>
10088            </body><a b={c()d}ˇ>
10089        "#
10090        .unindent(),
10091    );
10092    cx.update_editor(|editor, window, cx| {
10093        editor.handle_input(">", window, cx);
10094    });
10095    cx.assert_editor_state(
10096        &r#"
10097            <body><a b={c()d}>ˇ
10098                <script>
10099                    var x = 1;<a b={c()d}>ˇ
10100                </script>
10101            </body><a b={c()d}>ˇ
10102        "#
10103        .unindent(),
10104    );
10105
10106    // Reset
10107    cx.set_state(
10108        &r#"
10109            <body>ˇ
10110                <script>
10111                    var x = 1;ˇ
10112                </script>
10113            </body>ˇ
10114        "#
10115        .unindent(),
10116    );
10117
10118    cx.update_editor(|editor, window, cx| {
10119        editor.handle_input("<", window, cx);
10120    });
10121    cx.assert_editor_state(
10122        &r#"
10123            <body><ˇ>
10124                <script>
10125                    var x = 1;<ˇ
10126                </script>
10127            </body><ˇ>
10128        "#
10129        .unindent(),
10130    );
10131
10132    // When backspacing, the closing angle brackets are removed.
10133    cx.update_editor(|editor, window, cx| {
10134        editor.backspace(&Backspace, window, cx);
10135    });
10136    cx.assert_editor_state(
10137        &r#"
10138            <body>ˇ
10139                <script>
10140                    var x = 1;ˇ
10141                </script>
10142            </body>ˇ
10143        "#
10144        .unindent(),
10145    );
10146
10147    // Block comments autoclose in JavaScript, but not HTML.
10148    cx.update_editor(|editor, window, cx| {
10149        editor.handle_input("/", window, cx);
10150        editor.handle_input("*", window, cx);
10151    });
10152    cx.assert_editor_state(
10153        &r#"
10154            <body>/*ˇ
10155                <script>
10156                    var x = 1;/*ˇ */
10157                </script>
10158            </body>/*ˇ
10159        "#
10160        .unindent(),
10161    );
10162}
10163
10164#[gpui::test]
10165async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10166    init_test(cx, |_| {});
10167
10168    let mut cx = EditorTestContext::new(cx).await;
10169
10170    let rust_language = Arc::new(
10171        Language::new(
10172            LanguageConfig {
10173                name: "Rust".into(),
10174                brackets: serde_json::from_value(json!([
10175                    { "start": "{", "end": "}", "close": true, "newline": true },
10176                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10177                ]))
10178                .unwrap(),
10179                autoclose_before: "})]>".into(),
10180                ..Default::default()
10181            },
10182            Some(tree_sitter_rust::LANGUAGE.into()),
10183        )
10184        .with_override_query("(string_literal) @string")
10185        .unwrap(),
10186    );
10187
10188    cx.language_registry().add(rust_language.clone());
10189    cx.update_buffer(|buffer, cx| {
10190        buffer.set_language(Some(rust_language), cx);
10191    });
10192
10193    cx.set_state(
10194        &r#"
10195            let x = ˇ
10196        "#
10197        .unindent(),
10198    );
10199
10200    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10201    cx.update_editor(|editor, window, cx| {
10202        editor.handle_input("\"", window, cx);
10203    });
10204    cx.assert_editor_state(
10205        &r#"
10206            let x = "ˇ"
10207        "#
10208        .unindent(),
10209    );
10210
10211    // Inserting another quotation mark. The cursor moves across the existing
10212    // automatically-inserted quotation mark.
10213    cx.update_editor(|editor, window, cx| {
10214        editor.handle_input("\"", window, cx);
10215    });
10216    cx.assert_editor_state(
10217        &r#"
10218            let x = ""ˇ
10219        "#
10220        .unindent(),
10221    );
10222
10223    // Reset
10224    cx.set_state(
10225        &r#"
10226            let x = ˇ
10227        "#
10228        .unindent(),
10229    );
10230
10231    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
10232    cx.update_editor(|editor, window, cx| {
10233        editor.handle_input("\"", window, cx);
10234        editor.handle_input(" ", window, cx);
10235        editor.move_left(&Default::default(), window, cx);
10236        editor.handle_input("\\", window, cx);
10237        editor.handle_input("\"", window, cx);
10238    });
10239    cx.assert_editor_state(
10240        &r#"
10241            let x = "\"ˇ "
10242        "#
10243        .unindent(),
10244    );
10245
10246    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10247    // mark. Nothing is inserted.
10248    cx.update_editor(|editor, window, cx| {
10249        editor.move_right(&Default::default(), window, cx);
10250        editor.handle_input("\"", window, cx);
10251    });
10252    cx.assert_editor_state(
10253        &r#"
10254            let x = "\" "ˇ
10255        "#
10256        .unindent(),
10257    );
10258}
10259
10260#[gpui::test]
10261async fn test_surround_with_pair(cx: &mut TestAppContext) {
10262    init_test(cx, |_| {});
10263
10264    let language = Arc::new(Language::new(
10265        LanguageConfig {
10266            brackets: BracketPairConfig {
10267                pairs: vec![
10268                    BracketPair {
10269                        start: "{".to_string(),
10270                        end: "}".to_string(),
10271                        close: true,
10272                        surround: true,
10273                        newline: true,
10274                    },
10275                    BracketPair {
10276                        start: "/* ".to_string(),
10277                        end: "*/".to_string(),
10278                        close: true,
10279                        surround: true,
10280                        ..Default::default()
10281                    },
10282                ],
10283                ..Default::default()
10284            },
10285            ..Default::default()
10286        },
10287        Some(tree_sitter_rust::LANGUAGE.into()),
10288    ));
10289
10290    let text = r#"
10291        a
10292        b
10293        c
10294    "#
10295    .unindent();
10296
10297    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10298    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10299    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10300    editor
10301        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10302        .await;
10303
10304    editor.update_in(cx, |editor, window, cx| {
10305        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10306            s.select_display_ranges([
10307                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10308                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10309                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
10310            ])
10311        });
10312
10313        editor.handle_input("{", window, cx);
10314        editor.handle_input("{", window, cx);
10315        editor.handle_input("{", window, cx);
10316        assert_eq!(
10317            editor.text(cx),
10318            "
10319                {{{a}}}
10320                {{{b}}}
10321                {{{c}}}
10322            "
10323            .unindent()
10324        );
10325        assert_eq!(
10326            editor.selections.display_ranges(cx),
10327            [
10328                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
10329                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
10330                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
10331            ]
10332        );
10333
10334        editor.undo(&Undo, window, cx);
10335        editor.undo(&Undo, window, cx);
10336        editor.undo(&Undo, window, cx);
10337        assert_eq!(
10338            editor.text(cx),
10339            "
10340                a
10341                b
10342                c
10343            "
10344            .unindent()
10345        );
10346        assert_eq!(
10347            editor.selections.display_ranges(cx),
10348            [
10349                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10350                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10351                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10352            ]
10353        );
10354
10355        // Ensure inserting the first character of a multi-byte bracket pair
10356        // doesn't surround the selections with the bracket.
10357        editor.handle_input("/", window, cx);
10358        assert_eq!(
10359            editor.text(cx),
10360            "
10361                /
10362                /
10363                /
10364            "
10365            .unindent()
10366        );
10367        assert_eq!(
10368            editor.selections.display_ranges(cx),
10369            [
10370                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10371                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10372                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10373            ]
10374        );
10375
10376        editor.undo(&Undo, window, cx);
10377        assert_eq!(
10378            editor.text(cx),
10379            "
10380                a
10381                b
10382                c
10383            "
10384            .unindent()
10385        );
10386        assert_eq!(
10387            editor.selections.display_ranges(cx),
10388            [
10389                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10390                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10391                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10392            ]
10393        );
10394
10395        // Ensure inserting the last character of a multi-byte bracket pair
10396        // doesn't surround the selections with the bracket.
10397        editor.handle_input("*", window, cx);
10398        assert_eq!(
10399            editor.text(cx),
10400            "
10401                *
10402                *
10403                *
10404            "
10405            .unindent()
10406        );
10407        assert_eq!(
10408            editor.selections.display_ranges(cx),
10409            [
10410                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10411                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10412                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10413            ]
10414        );
10415    });
10416}
10417
10418#[gpui::test]
10419async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
10420    init_test(cx, |_| {});
10421
10422    let language = Arc::new(Language::new(
10423        LanguageConfig {
10424            brackets: BracketPairConfig {
10425                pairs: vec![BracketPair {
10426                    start: "{".to_string(),
10427                    end: "}".to_string(),
10428                    close: true,
10429                    surround: true,
10430                    newline: true,
10431                }],
10432                ..Default::default()
10433            },
10434            autoclose_before: "}".to_string(),
10435            ..Default::default()
10436        },
10437        Some(tree_sitter_rust::LANGUAGE.into()),
10438    ));
10439
10440    let text = r#"
10441        a
10442        b
10443        c
10444    "#
10445    .unindent();
10446
10447    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10448    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10449    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10450    editor
10451        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10452        .await;
10453
10454    editor.update_in(cx, |editor, window, cx| {
10455        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10456            s.select_ranges([
10457                Point::new(0, 1)..Point::new(0, 1),
10458                Point::new(1, 1)..Point::new(1, 1),
10459                Point::new(2, 1)..Point::new(2, 1),
10460            ])
10461        });
10462
10463        editor.handle_input("{", window, cx);
10464        editor.handle_input("{", window, cx);
10465        editor.handle_input("_", window, cx);
10466        assert_eq!(
10467            editor.text(cx),
10468            "
10469                a{{_}}
10470                b{{_}}
10471                c{{_}}
10472            "
10473            .unindent()
10474        );
10475        assert_eq!(
10476            editor.selections.ranges::<Point>(cx),
10477            [
10478                Point::new(0, 4)..Point::new(0, 4),
10479                Point::new(1, 4)..Point::new(1, 4),
10480                Point::new(2, 4)..Point::new(2, 4)
10481            ]
10482        );
10483
10484        editor.backspace(&Default::default(), window, cx);
10485        editor.backspace(&Default::default(), window, cx);
10486        assert_eq!(
10487            editor.text(cx),
10488            "
10489                a{}
10490                b{}
10491                c{}
10492            "
10493            .unindent()
10494        );
10495        assert_eq!(
10496            editor.selections.ranges::<Point>(cx),
10497            [
10498                Point::new(0, 2)..Point::new(0, 2),
10499                Point::new(1, 2)..Point::new(1, 2),
10500                Point::new(2, 2)..Point::new(2, 2)
10501            ]
10502        );
10503
10504        editor.delete_to_previous_word_start(&Default::default(), window, cx);
10505        assert_eq!(
10506            editor.text(cx),
10507            "
10508                a
10509                b
10510                c
10511            "
10512            .unindent()
10513        );
10514        assert_eq!(
10515            editor.selections.ranges::<Point>(cx),
10516            [
10517                Point::new(0, 1)..Point::new(0, 1),
10518                Point::new(1, 1)..Point::new(1, 1),
10519                Point::new(2, 1)..Point::new(2, 1)
10520            ]
10521        );
10522    });
10523}
10524
10525#[gpui::test]
10526async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
10527    init_test(cx, |settings| {
10528        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10529    });
10530
10531    let mut cx = EditorTestContext::new(cx).await;
10532
10533    let language = Arc::new(Language::new(
10534        LanguageConfig {
10535            brackets: BracketPairConfig {
10536                pairs: vec![
10537                    BracketPair {
10538                        start: "{".to_string(),
10539                        end: "}".to_string(),
10540                        close: true,
10541                        surround: true,
10542                        newline: true,
10543                    },
10544                    BracketPair {
10545                        start: "(".to_string(),
10546                        end: ")".to_string(),
10547                        close: true,
10548                        surround: true,
10549                        newline: true,
10550                    },
10551                    BracketPair {
10552                        start: "[".to_string(),
10553                        end: "]".to_string(),
10554                        close: false,
10555                        surround: true,
10556                        newline: true,
10557                    },
10558                ],
10559                ..Default::default()
10560            },
10561            autoclose_before: "})]".to_string(),
10562            ..Default::default()
10563        },
10564        Some(tree_sitter_rust::LANGUAGE.into()),
10565    ));
10566
10567    cx.language_registry().add(language.clone());
10568    cx.update_buffer(|buffer, cx| {
10569        buffer.set_language(Some(language), cx);
10570    });
10571
10572    cx.set_state(
10573        &"
10574            {(ˇ)}
10575            [[ˇ]]
10576            {(ˇ)}
10577        "
10578        .unindent(),
10579    );
10580
10581    cx.update_editor(|editor, window, cx| {
10582        editor.backspace(&Default::default(), window, cx);
10583        editor.backspace(&Default::default(), window, cx);
10584    });
10585
10586    cx.assert_editor_state(
10587        &"
10588            ˇ
10589            ˇ]]
10590            ˇ
10591        "
10592        .unindent(),
10593    );
10594
10595    cx.update_editor(|editor, window, cx| {
10596        editor.handle_input("{", window, cx);
10597        editor.handle_input("{", window, cx);
10598        editor.move_right(&MoveRight, window, cx);
10599        editor.move_right(&MoveRight, window, cx);
10600        editor.move_left(&MoveLeft, window, cx);
10601        editor.move_left(&MoveLeft, window, cx);
10602        editor.backspace(&Default::default(), window, cx);
10603    });
10604
10605    cx.assert_editor_state(
10606        &"
10607            {ˇ}
10608            {ˇ}]]
10609            {ˇ}
10610        "
10611        .unindent(),
10612    );
10613
10614    cx.update_editor(|editor, window, cx| {
10615        editor.backspace(&Default::default(), window, cx);
10616    });
10617
10618    cx.assert_editor_state(
10619        &"
10620            ˇ
10621            ˇ]]
10622            ˇ
10623        "
10624        .unindent(),
10625    );
10626}
10627
10628#[gpui::test]
10629async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
10630    init_test(cx, |_| {});
10631
10632    let language = Arc::new(Language::new(
10633        LanguageConfig::default(),
10634        Some(tree_sitter_rust::LANGUAGE.into()),
10635    ));
10636
10637    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
10638    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10639    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10640    editor
10641        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10642        .await;
10643
10644    editor.update_in(cx, |editor, window, cx| {
10645        editor.set_auto_replace_emoji_shortcode(true);
10646
10647        editor.handle_input("Hello ", window, cx);
10648        editor.handle_input(":wave", window, cx);
10649        assert_eq!(editor.text(cx), "Hello :wave".unindent());
10650
10651        editor.handle_input(":", window, cx);
10652        assert_eq!(editor.text(cx), "Hello 👋".unindent());
10653
10654        editor.handle_input(" :smile", window, cx);
10655        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
10656
10657        editor.handle_input(":", window, cx);
10658        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
10659
10660        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
10661        editor.handle_input(":wave", window, cx);
10662        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
10663
10664        editor.handle_input(":", window, cx);
10665        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
10666
10667        editor.handle_input(":1", window, cx);
10668        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
10669
10670        editor.handle_input(":", window, cx);
10671        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
10672
10673        // Ensure shortcode does not get replaced when it is part of a word
10674        editor.handle_input(" Test:wave", window, cx);
10675        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
10676
10677        editor.handle_input(":", window, cx);
10678        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
10679
10680        editor.set_auto_replace_emoji_shortcode(false);
10681
10682        // Ensure shortcode does not get replaced when auto replace is off
10683        editor.handle_input(" :wave", window, cx);
10684        assert_eq!(
10685            editor.text(cx),
10686            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
10687        );
10688
10689        editor.handle_input(":", window, cx);
10690        assert_eq!(
10691            editor.text(cx),
10692            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
10693        );
10694    });
10695}
10696
10697#[gpui::test]
10698async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
10699    init_test(cx, |_| {});
10700
10701    let (text, insertion_ranges) = marked_text_ranges(
10702        indoc! {"
10703            ˇ
10704        "},
10705        false,
10706    );
10707
10708    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
10709    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10710
10711    _ = editor.update_in(cx, |editor, window, cx| {
10712        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
10713
10714        editor
10715            .insert_snippet(&insertion_ranges, snippet, window, cx)
10716            .unwrap();
10717
10718        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
10719            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
10720            assert_eq!(editor.text(cx), expected_text);
10721            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
10722        }
10723
10724        assert(
10725            editor,
10726            cx,
10727            indoc! {"
10728            type «» =•
10729            "},
10730        );
10731
10732        assert!(editor.context_menu_visible(), "There should be a matches");
10733    });
10734}
10735
10736#[gpui::test]
10737async fn test_snippets(cx: &mut TestAppContext) {
10738    init_test(cx, |_| {});
10739
10740    let mut cx = EditorTestContext::new(cx).await;
10741
10742    cx.set_state(indoc! {"
10743        a.ˇ b
10744        a.ˇ b
10745        a.ˇ b
10746    "});
10747
10748    cx.update_editor(|editor, window, cx| {
10749        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
10750        let insertion_ranges = editor
10751            .selections
10752            .all(cx)
10753            .iter()
10754            .map(|s| s.range())
10755            .collect::<Vec<_>>();
10756        editor
10757            .insert_snippet(&insertion_ranges, snippet, window, cx)
10758            .unwrap();
10759    });
10760
10761    cx.assert_editor_state(indoc! {"
10762        a.f(«oneˇ», two, «threeˇ») b
10763        a.f(«oneˇ», two, «threeˇ») b
10764        a.f(«oneˇ», two, «threeˇ») b
10765    "});
10766
10767    // Can't move earlier than the first tab stop
10768    cx.update_editor(|editor, window, cx| {
10769        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10770    });
10771    cx.assert_editor_state(indoc! {"
10772        a.f(«oneˇ», two, «threeˇ») b
10773        a.f(«oneˇ», two, «threeˇ») b
10774        a.f(«oneˇ», two, «threeˇ») b
10775    "});
10776
10777    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10778    cx.assert_editor_state(indoc! {"
10779        a.f(one, «twoˇ», three) b
10780        a.f(one, «twoˇ», three) b
10781        a.f(one, «twoˇ», three) b
10782    "});
10783
10784    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
10785    cx.assert_editor_state(indoc! {"
10786        a.f(«oneˇ», two, «threeˇ») b
10787        a.f(«oneˇ», two, «threeˇ») b
10788        a.f(«oneˇ», two, «threeˇ») b
10789    "});
10790
10791    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10792    cx.assert_editor_state(indoc! {"
10793        a.f(one, «twoˇ», three) b
10794        a.f(one, «twoˇ», three) b
10795        a.f(one, «twoˇ», three) b
10796    "});
10797    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10798    cx.assert_editor_state(indoc! {"
10799        a.f(one, two, three)ˇ b
10800        a.f(one, two, three)ˇ b
10801        a.f(one, two, three)ˇ b
10802    "});
10803
10804    // As soon as the last tab stop is reached, snippet state is gone
10805    cx.update_editor(|editor, window, cx| {
10806        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10807    });
10808    cx.assert_editor_state(indoc! {"
10809        a.f(one, two, three)ˇ b
10810        a.f(one, two, three)ˇ b
10811        a.f(one, two, three)ˇ b
10812    "});
10813}
10814
10815#[gpui::test]
10816async fn test_snippet_indentation(cx: &mut TestAppContext) {
10817    init_test(cx, |_| {});
10818
10819    let mut cx = EditorTestContext::new(cx).await;
10820
10821    cx.update_editor(|editor, window, cx| {
10822        let snippet = Snippet::parse(indoc! {"
10823            /*
10824             * Multiline comment with leading indentation
10825             *
10826             * $1
10827             */
10828            $0"})
10829        .unwrap();
10830        let insertion_ranges = editor
10831            .selections
10832            .all(cx)
10833            .iter()
10834            .map(|s| s.range())
10835            .collect::<Vec<_>>();
10836        editor
10837            .insert_snippet(&insertion_ranges, snippet, window, cx)
10838            .unwrap();
10839    });
10840
10841    cx.assert_editor_state(indoc! {"
10842        /*
10843         * Multiline comment with leading indentation
10844         *
10845         * ˇ
10846         */
10847    "});
10848
10849    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10850    cx.assert_editor_state(indoc! {"
10851        /*
10852         * Multiline comment with leading indentation
10853         *
10854         *•
10855         */
10856        ˇ"});
10857}
10858
10859#[gpui::test]
10860async fn test_document_format_during_save(cx: &mut TestAppContext) {
10861    init_test(cx, |_| {});
10862
10863    let fs = FakeFs::new(cx.executor());
10864    fs.insert_file(path!("/file.rs"), Default::default()).await;
10865
10866    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
10867
10868    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10869    language_registry.add(rust_lang());
10870    let mut fake_servers = language_registry.register_fake_lsp(
10871        "Rust",
10872        FakeLspAdapter {
10873            capabilities: lsp::ServerCapabilities {
10874                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10875                ..Default::default()
10876            },
10877            ..Default::default()
10878        },
10879    );
10880
10881    let buffer = project
10882        .update(cx, |project, cx| {
10883            project.open_local_buffer(path!("/file.rs"), cx)
10884        })
10885        .await
10886        .unwrap();
10887
10888    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10889    let (editor, cx) = cx.add_window_view(|window, cx| {
10890        build_editor_with_project(project.clone(), buffer, window, cx)
10891    });
10892    editor.update_in(cx, |editor, window, cx| {
10893        editor.set_text("one\ntwo\nthree\n", window, cx)
10894    });
10895    assert!(cx.read(|cx| editor.is_dirty(cx)));
10896
10897    cx.executor().start_waiting();
10898    let fake_server = fake_servers.next().await.unwrap();
10899
10900    {
10901        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10902            move |params, _| async move {
10903                assert_eq!(
10904                    params.text_document.uri,
10905                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10906                );
10907                assert_eq!(params.options.tab_size, 4);
10908                Ok(Some(vec![lsp::TextEdit::new(
10909                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10910                    ", ".to_string(),
10911                )]))
10912            },
10913        );
10914        let save = editor
10915            .update_in(cx, |editor, window, cx| {
10916                editor.save(
10917                    SaveOptions {
10918                        format: true,
10919                        autosave: false,
10920                    },
10921                    project.clone(),
10922                    window,
10923                    cx,
10924                )
10925            })
10926            .unwrap();
10927        cx.executor().start_waiting();
10928        save.await;
10929
10930        assert_eq!(
10931            editor.update(cx, |editor, cx| editor.text(cx)),
10932            "one, two\nthree\n"
10933        );
10934        assert!(!cx.read(|cx| editor.is_dirty(cx)));
10935    }
10936
10937    {
10938        editor.update_in(cx, |editor, window, cx| {
10939            editor.set_text("one\ntwo\nthree\n", window, cx)
10940        });
10941        assert!(cx.read(|cx| editor.is_dirty(cx)));
10942
10943        // Ensure we can still save even if formatting hangs.
10944        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10945            move |params, _| async move {
10946                assert_eq!(
10947                    params.text_document.uri,
10948                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10949                );
10950                futures::future::pending::<()>().await;
10951                unreachable!()
10952            },
10953        );
10954        let save = editor
10955            .update_in(cx, |editor, window, cx| {
10956                editor.save(
10957                    SaveOptions {
10958                        format: true,
10959                        autosave: false,
10960                    },
10961                    project.clone(),
10962                    window,
10963                    cx,
10964                )
10965            })
10966            .unwrap();
10967        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10968        cx.executor().start_waiting();
10969        save.await;
10970        assert_eq!(
10971            editor.update(cx, |editor, cx| editor.text(cx)),
10972            "one\ntwo\nthree\n"
10973        );
10974    }
10975
10976    // Set rust language override and assert overridden tabsize is sent to language server
10977    update_test_language_settings(cx, |settings| {
10978        settings.languages.0.insert(
10979            "Rust".into(),
10980            LanguageSettingsContent {
10981                tab_size: NonZeroU32::new(8),
10982                ..Default::default()
10983            },
10984        );
10985    });
10986
10987    {
10988        editor.update_in(cx, |editor, window, cx| {
10989            editor.set_text("somehting_new\n", window, cx)
10990        });
10991        assert!(cx.read(|cx| editor.is_dirty(cx)));
10992        let _formatting_request_signal = fake_server
10993            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10994                assert_eq!(
10995                    params.text_document.uri,
10996                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10997                );
10998                assert_eq!(params.options.tab_size, 8);
10999                Ok(Some(vec![]))
11000            });
11001        let save = editor
11002            .update_in(cx, |editor, window, cx| {
11003                editor.save(
11004                    SaveOptions {
11005                        format: true,
11006                        autosave: false,
11007                    },
11008                    project.clone(),
11009                    window,
11010                    cx,
11011                )
11012            })
11013            .unwrap();
11014        cx.executor().start_waiting();
11015        save.await;
11016    }
11017}
11018
11019#[gpui::test]
11020async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11021    init_test(cx, |settings| {
11022        settings.defaults.ensure_final_newline_on_save = Some(false);
11023    });
11024
11025    let fs = FakeFs::new(cx.executor());
11026    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11027
11028    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11029
11030    let buffer = project
11031        .update(cx, |project, cx| {
11032            project.open_local_buffer(path!("/file.txt"), cx)
11033        })
11034        .await
11035        .unwrap();
11036
11037    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11038    let (editor, cx) = cx.add_window_view(|window, cx| {
11039        build_editor_with_project(project.clone(), buffer, window, cx)
11040    });
11041    editor.update_in(cx, |editor, window, cx| {
11042        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11043            s.select_ranges([0..0])
11044        });
11045    });
11046    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11047
11048    editor.update_in(cx, |editor, window, cx| {
11049        editor.handle_input("\n", window, cx)
11050    });
11051    cx.run_until_parked();
11052    save(&editor, &project, cx).await;
11053    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11054
11055    editor.update_in(cx, |editor, window, cx| {
11056        editor.undo(&Default::default(), window, cx);
11057    });
11058    save(&editor, &project, cx).await;
11059    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11060
11061    editor.update_in(cx, |editor, window, cx| {
11062        editor.redo(&Default::default(), window, cx);
11063    });
11064    cx.run_until_parked();
11065    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11066
11067    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11068        let save = editor
11069            .update_in(cx, |editor, window, cx| {
11070                editor.save(
11071                    SaveOptions {
11072                        format: true,
11073                        autosave: false,
11074                    },
11075                    project.clone(),
11076                    window,
11077                    cx,
11078                )
11079            })
11080            .unwrap();
11081        cx.executor().start_waiting();
11082        save.await;
11083        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11084    }
11085}
11086
11087#[gpui::test]
11088async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
11089    init_test(cx, |_| {});
11090
11091    let cols = 4;
11092    let rows = 10;
11093    let sample_text_1 = sample_text(rows, cols, 'a');
11094    assert_eq!(
11095        sample_text_1,
11096        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11097    );
11098    let sample_text_2 = sample_text(rows, cols, 'l');
11099    assert_eq!(
11100        sample_text_2,
11101        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11102    );
11103    let sample_text_3 = sample_text(rows, cols, 'v');
11104    assert_eq!(
11105        sample_text_3,
11106        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11107    );
11108
11109    let fs = FakeFs::new(cx.executor());
11110    fs.insert_tree(
11111        path!("/a"),
11112        json!({
11113            "main.rs": sample_text_1,
11114            "other.rs": sample_text_2,
11115            "lib.rs": sample_text_3,
11116        }),
11117    )
11118    .await;
11119
11120    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11121    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11122    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11123
11124    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11125    language_registry.add(rust_lang());
11126    let mut fake_servers = language_registry.register_fake_lsp(
11127        "Rust",
11128        FakeLspAdapter {
11129            capabilities: lsp::ServerCapabilities {
11130                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11131                ..Default::default()
11132            },
11133            ..Default::default()
11134        },
11135    );
11136
11137    let worktree = project.update(cx, |project, cx| {
11138        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
11139        assert_eq!(worktrees.len(), 1);
11140        worktrees.pop().unwrap()
11141    });
11142    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11143
11144    let buffer_1 = project
11145        .update(cx, |project, cx| {
11146            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
11147        })
11148        .await
11149        .unwrap();
11150    let buffer_2 = project
11151        .update(cx, |project, cx| {
11152            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
11153        })
11154        .await
11155        .unwrap();
11156    let buffer_3 = project
11157        .update(cx, |project, cx| {
11158            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
11159        })
11160        .await
11161        .unwrap();
11162
11163    let multi_buffer = cx.new(|cx| {
11164        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11165        multi_buffer.push_excerpts(
11166            buffer_1.clone(),
11167            [
11168                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11169                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11170                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11171            ],
11172            cx,
11173        );
11174        multi_buffer.push_excerpts(
11175            buffer_2.clone(),
11176            [
11177                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11178                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11179                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11180            ],
11181            cx,
11182        );
11183        multi_buffer.push_excerpts(
11184            buffer_3.clone(),
11185            [
11186                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11187                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11188                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11189            ],
11190            cx,
11191        );
11192        multi_buffer
11193    });
11194    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
11195        Editor::new(
11196            EditorMode::full(),
11197            multi_buffer,
11198            Some(project.clone()),
11199            window,
11200            cx,
11201        )
11202    });
11203
11204    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11205        editor.change_selections(
11206            SelectionEffects::scroll(Autoscroll::Next),
11207            window,
11208            cx,
11209            |s| s.select_ranges(Some(1..2)),
11210        );
11211        editor.insert("|one|two|three|", window, cx);
11212    });
11213    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11214    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11215        editor.change_selections(
11216            SelectionEffects::scroll(Autoscroll::Next),
11217            window,
11218            cx,
11219            |s| s.select_ranges(Some(60..70)),
11220        );
11221        editor.insert("|four|five|six|", window, cx);
11222    });
11223    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11224
11225    // First two buffers should be edited, but not the third one.
11226    assert_eq!(
11227        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11228        "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}",
11229    );
11230    buffer_1.update(cx, |buffer, _| {
11231        assert!(buffer.is_dirty());
11232        assert_eq!(
11233            buffer.text(),
11234            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
11235        )
11236    });
11237    buffer_2.update(cx, |buffer, _| {
11238        assert!(buffer.is_dirty());
11239        assert_eq!(
11240            buffer.text(),
11241            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
11242        )
11243    });
11244    buffer_3.update(cx, |buffer, _| {
11245        assert!(!buffer.is_dirty());
11246        assert_eq!(buffer.text(), sample_text_3,)
11247    });
11248    cx.executor().run_until_parked();
11249
11250    cx.executor().start_waiting();
11251    let save = multi_buffer_editor
11252        .update_in(cx, |editor, window, cx| {
11253            editor.save(
11254                SaveOptions {
11255                    format: true,
11256                    autosave: false,
11257                },
11258                project.clone(),
11259                window,
11260                cx,
11261            )
11262        })
11263        .unwrap();
11264
11265    let fake_server = fake_servers.next().await.unwrap();
11266    fake_server
11267        .server
11268        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11269            Ok(Some(vec![lsp::TextEdit::new(
11270                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11271                format!("[{} formatted]", params.text_document.uri),
11272            )]))
11273        })
11274        .detach();
11275    save.await;
11276
11277    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11278    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11279    assert_eq!(
11280        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11281        uri!(
11282            "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}"
11283        ),
11284    );
11285    buffer_1.update(cx, |buffer, _| {
11286        assert!(!buffer.is_dirty());
11287        assert_eq!(
11288            buffer.text(),
11289            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11290        )
11291    });
11292    buffer_2.update(cx, |buffer, _| {
11293        assert!(!buffer.is_dirty());
11294        assert_eq!(
11295            buffer.text(),
11296            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11297        )
11298    });
11299    buffer_3.update(cx, |buffer, _| {
11300        assert!(!buffer.is_dirty());
11301        assert_eq!(buffer.text(), sample_text_3,)
11302    });
11303}
11304
11305#[gpui::test]
11306async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
11307    init_test(cx, |_| {});
11308
11309    let fs = FakeFs::new(cx.executor());
11310    fs.insert_tree(
11311        path!("/dir"),
11312        json!({
11313            "file1.rs": "fn main() { println!(\"hello\"); }",
11314            "file2.rs": "fn test() { println!(\"test\"); }",
11315            "file3.rs": "fn other() { println!(\"other\"); }\n",
11316        }),
11317    )
11318    .await;
11319
11320    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
11321    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11322    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11323
11324    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11325    language_registry.add(rust_lang());
11326
11327    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
11328    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11329
11330    // Open three buffers
11331    let buffer_1 = project
11332        .update(cx, |project, cx| {
11333            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
11334        })
11335        .await
11336        .unwrap();
11337    let buffer_2 = project
11338        .update(cx, |project, cx| {
11339            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
11340        })
11341        .await
11342        .unwrap();
11343    let buffer_3 = project
11344        .update(cx, |project, cx| {
11345            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
11346        })
11347        .await
11348        .unwrap();
11349
11350    // Create a multi-buffer with all three buffers
11351    let multi_buffer = cx.new(|cx| {
11352        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11353        multi_buffer.push_excerpts(
11354            buffer_1.clone(),
11355            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11356            cx,
11357        );
11358        multi_buffer.push_excerpts(
11359            buffer_2.clone(),
11360            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11361            cx,
11362        );
11363        multi_buffer.push_excerpts(
11364            buffer_3.clone(),
11365            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11366            cx,
11367        );
11368        multi_buffer
11369    });
11370
11371    let editor = cx.new_window_entity(|window, cx| {
11372        Editor::new(
11373            EditorMode::full(),
11374            multi_buffer,
11375            Some(project.clone()),
11376            window,
11377            cx,
11378        )
11379    });
11380
11381    // Edit only the first buffer
11382    editor.update_in(cx, |editor, window, cx| {
11383        editor.change_selections(
11384            SelectionEffects::scroll(Autoscroll::Next),
11385            window,
11386            cx,
11387            |s| s.select_ranges(Some(10..10)),
11388        );
11389        editor.insert("// edited", window, cx);
11390    });
11391
11392    // Verify that only buffer 1 is dirty
11393    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
11394    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11395    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11396
11397    // Get write counts after file creation (files were created with initial content)
11398    // We expect each file to have been written once during creation
11399    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
11400    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
11401    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
11402
11403    // Perform autosave
11404    let save_task = editor.update_in(cx, |editor, window, cx| {
11405        editor.save(
11406            SaveOptions {
11407                format: true,
11408                autosave: true,
11409            },
11410            project.clone(),
11411            window,
11412            cx,
11413        )
11414    });
11415    save_task.await.unwrap();
11416
11417    // Only the dirty buffer should have been saved
11418    assert_eq!(
11419        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11420        1,
11421        "Buffer 1 was dirty, so it should have been written once during autosave"
11422    );
11423    assert_eq!(
11424        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11425        0,
11426        "Buffer 2 was clean, so it should not have been written during autosave"
11427    );
11428    assert_eq!(
11429        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11430        0,
11431        "Buffer 3 was clean, so it should not have been written during autosave"
11432    );
11433
11434    // Verify buffer states after autosave
11435    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11436    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11437    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11438
11439    // Now perform a manual save (format = true)
11440    let save_task = editor.update_in(cx, |editor, window, cx| {
11441        editor.save(
11442            SaveOptions {
11443                format: true,
11444                autosave: false,
11445            },
11446            project.clone(),
11447            window,
11448            cx,
11449        )
11450    });
11451    save_task.await.unwrap();
11452
11453    // During manual save, clean buffers don't get written to disk
11454    // They just get did_save called for language server notifications
11455    assert_eq!(
11456        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11457        1,
11458        "Buffer 1 should only have been written once total (during autosave, not manual save)"
11459    );
11460    assert_eq!(
11461        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11462        0,
11463        "Buffer 2 should not have been written at all"
11464    );
11465    assert_eq!(
11466        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11467        0,
11468        "Buffer 3 should not have been written at all"
11469    );
11470}
11471
11472async fn setup_range_format_test(
11473    cx: &mut TestAppContext,
11474) -> (
11475    Entity<Project>,
11476    Entity<Editor>,
11477    &mut gpui::VisualTestContext,
11478    lsp::FakeLanguageServer,
11479) {
11480    init_test(cx, |_| {});
11481
11482    let fs = FakeFs::new(cx.executor());
11483    fs.insert_file(path!("/file.rs"), Default::default()).await;
11484
11485    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11486
11487    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11488    language_registry.add(rust_lang());
11489    let mut fake_servers = language_registry.register_fake_lsp(
11490        "Rust",
11491        FakeLspAdapter {
11492            capabilities: lsp::ServerCapabilities {
11493                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
11494                ..lsp::ServerCapabilities::default()
11495            },
11496            ..FakeLspAdapter::default()
11497        },
11498    );
11499
11500    let buffer = project
11501        .update(cx, |project, cx| {
11502            project.open_local_buffer(path!("/file.rs"), cx)
11503        })
11504        .await
11505        .unwrap();
11506
11507    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11508    let (editor, cx) = cx.add_window_view(|window, cx| {
11509        build_editor_with_project(project.clone(), buffer, window, cx)
11510    });
11511
11512    cx.executor().start_waiting();
11513    let fake_server = fake_servers.next().await.unwrap();
11514
11515    (project, editor, cx, fake_server)
11516}
11517
11518#[gpui::test]
11519async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
11520    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11521
11522    editor.update_in(cx, |editor, window, cx| {
11523        editor.set_text("one\ntwo\nthree\n", window, cx)
11524    });
11525    assert!(cx.read(|cx| editor.is_dirty(cx)));
11526
11527    let save = editor
11528        .update_in(cx, |editor, window, cx| {
11529            editor.save(
11530                SaveOptions {
11531                    format: true,
11532                    autosave: false,
11533                },
11534                project.clone(),
11535                window,
11536                cx,
11537            )
11538        })
11539        .unwrap();
11540    fake_server
11541        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11542            assert_eq!(
11543                params.text_document.uri,
11544                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11545            );
11546            assert_eq!(params.options.tab_size, 4);
11547            Ok(Some(vec![lsp::TextEdit::new(
11548                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11549                ", ".to_string(),
11550            )]))
11551        })
11552        .next()
11553        .await;
11554    cx.executor().start_waiting();
11555    save.await;
11556    assert_eq!(
11557        editor.update(cx, |editor, cx| editor.text(cx)),
11558        "one, two\nthree\n"
11559    );
11560    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11561}
11562
11563#[gpui::test]
11564async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
11565    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11566
11567    editor.update_in(cx, |editor, window, cx| {
11568        editor.set_text("one\ntwo\nthree\n", window, cx)
11569    });
11570    assert!(cx.read(|cx| editor.is_dirty(cx)));
11571
11572    // Test that save still works when formatting hangs
11573    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
11574        move |params, _| async move {
11575            assert_eq!(
11576                params.text_document.uri,
11577                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11578            );
11579            futures::future::pending::<()>().await;
11580            unreachable!()
11581        },
11582    );
11583    let save = editor
11584        .update_in(cx, |editor, window, cx| {
11585            editor.save(
11586                SaveOptions {
11587                    format: true,
11588                    autosave: false,
11589                },
11590                project.clone(),
11591                window,
11592                cx,
11593            )
11594        })
11595        .unwrap();
11596    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11597    cx.executor().start_waiting();
11598    save.await;
11599    assert_eq!(
11600        editor.update(cx, |editor, cx| editor.text(cx)),
11601        "one\ntwo\nthree\n"
11602    );
11603    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11604}
11605
11606#[gpui::test]
11607async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
11608    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11609
11610    // Buffer starts clean, no formatting should be requested
11611    let save = editor
11612        .update_in(cx, |editor, window, cx| {
11613            editor.save(
11614                SaveOptions {
11615                    format: false,
11616                    autosave: false,
11617                },
11618                project.clone(),
11619                window,
11620                cx,
11621            )
11622        })
11623        .unwrap();
11624    let _pending_format_request = fake_server
11625        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
11626            panic!("Should not be invoked");
11627        })
11628        .next();
11629    cx.executor().start_waiting();
11630    save.await;
11631    cx.run_until_parked();
11632}
11633
11634#[gpui::test]
11635async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
11636    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11637
11638    // Set Rust language override and assert overridden tabsize is sent to language server
11639    update_test_language_settings(cx, |settings| {
11640        settings.languages.0.insert(
11641            "Rust".into(),
11642            LanguageSettingsContent {
11643                tab_size: NonZeroU32::new(8),
11644                ..Default::default()
11645            },
11646        );
11647    });
11648
11649    editor.update_in(cx, |editor, window, cx| {
11650        editor.set_text("something_new\n", window, cx)
11651    });
11652    assert!(cx.read(|cx| editor.is_dirty(cx)));
11653    let save = editor
11654        .update_in(cx, |editor, window, cx| {
11655            editor.save(
11656                SaveOptions {
11657                    format: true,
11658                    autosave: false,
11659                },
11660                project.clone(),
11661                window,
11662                cx,
11663            )
11664        })
11665        .unwrap();
11666    fake_server
11667        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11668            assert_eq!(
11669                params.text_document.uri,
11670                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11671            );
11672            assert_eq!(params.options.tab_size, 8);
11673            Ok(Some(Vec::new()))
11674        })
11675        .next()
11676        .await;
11677    save.await;
11678}
11679
11680#[gpui::test]
11681async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
11682    init_test(cx, |settings| {
11683        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
11684            Formatter::LanguageServer { name: None },
11685        )))
11686    });
11687
11688    let fs = FakeFs::new(cx.executor());
11689    fs.insert_file(path!("/file.rs"), Default::default()).await;
11690
11691    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11692
11693    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11694    language_registry.add(Arc::new(Language::new(
11695        LanguageConfig {
11696            name: "Rust".into(),
11697            matcher: LanguageMatcher {
11698                path_suffixes: vec!["rs".to_string()],
11699                ..Default::default()
11700            },
11701            ..LanguageConfig::default()
11702        },
11703        Some(tree_sitter_rust::LANGUAGE.into()),
11704    )));
11705    update_test_language_settings(cx, |settings| {
11706        // Enable Prettier formatting for the same buffer, and ensure
11707        // LSP is called instead of Prettier.
11708        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
11709    });
11710    let mut fake_servers = language_registry.register_fake_lsp(
11711        "Rust",
11712        FakeLspAdapter {
11713            capabilities: lsp::ServerCapabilities {
11714                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11715                ..Default::default()
11716            },
11717            ..Default::default()
11718        },
11719    );
11720
11721    let buffer = project
11722        .update(cx, |project, cx| {
11723            project.open_local_buffer(path!("/file.rs"), cx)
11724        })
11725        .await
11726        .unwrap();
11727
11728    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11729    let (editor, cx) = cx.add_window_view(|window, cx| {
11730        build_editor_with_project(project.clone(), buffer, window, cx)
11731    });
11732    editor.update_in(cx, |editor, window, cx| {
11733        editor.set_text("one\ntwo\nthree\n", window, cx)
11734    });
11735
11736    cx.executor().start_waiting();
11737    let fake_server = fake_servers.next().await.unwrap();
11738
11739    let format = editor
11740        .update_in(cx, |editor, window, cx| {
11741            editor.perform_format(
11742                project.clone(),
11743                FormatTrigger::Manual,
11744                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11745                window,
11746                cx,
11747            )
11748        })
11749        .unwrap();
11750    fake_server
11751        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11752            assert_eq!(
11753                params.text_document.uri,
11754                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11755            );
11756            assert_eq!(params.options.tab_size, 4);
11757            Ok(Some(vec![lsp::TextEdit::new(
11758                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11759                ", ".to_string(),
11760            )]))
11761        })
11762        .next()
11763        .await;
11764    cx.executor().start_waiting();
11765    format.await;
11766    assert_eq!(
11767        editor.update(cx, |editor, cx| editor.text(cx)),
11768        "one, two\nthree\n"
11769    );
11770
11771    editor.update_in(cx, |editor, window, cx| {
11772        editor.set_text("one\ntwo\nthree\n", window, cx)
11773    });
11774    // Ensure we don't lock if formatting hangs.
11775    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11776        move |params, _| async move {
11777            assert_eq!(
11778                params.text_document.uri,
11779                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11780            );
11781            futures::future::pending::<()>().await;
11782            unreachable!()
11783        },
11784    );
11785    let format = editor
11786        .update_in(cx, |editor, window, cx| {
11787            editor.perform_format(
11788                project,
11789                FormatTrigger::Manual,
11790                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11791                window,
11792                cx,
11793            )
11794        })
11795        .unwrap();
11796    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11797    cx.executor().start_waiting();
11798    format.await;
11799    assert_eq!(
11800        editor.update(cx, |editor, cx| editor.text(cx)),
11801        "one\ntwo\nthree\n"
11802    );
11803}
11804
11805#[gpui::test]
11806async fn test_multiple_formatters(cx: &mut TestAppContext) {
11807    init_test(cx, |settings| {
11808        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
11809        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
11810            Formatter::LanguageServer { name: None },
11811            Formatter::CodeActions(
11812                [
11813                    ("code-action-1".into(), true),
11814                    ("code-action-2".into(), true),
11815                ]
11816                .into_iter()
11817                .collect(),
11818            ),
11819        ])))
11820    });
11821
11822    let fs = FakeFs::new(cx.executor());
11823    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
11824        .await;
11825
11826    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11827    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11828    language_registry.add(rust_lang());
11829
11830    let mut fake_servers = language_registry.register_fake_lsp(
11831        "Rust",
11832        FakeLspAdapter {
11833            capabilities: lsp::ServerCapabilities {
11834                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11835                execute_command_provider: Some(lsp::ExecuteCommandOptions {
11836                    commands: vec!["the-command-for-code-action-1".into()],
11837                    ..Default::default()
11838                }),
11839                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
11840                ..Default::default()
11841            },
11842            ..Default::default()
11843        },
11844    );
11845
11846    let buffer = project
11847        .update(cx, |project, cx| {
11848            project.open_local_buffer(path!("/file.rs"), cx)
11849        })
11850        .await
11851        .unwrap();
11852
11853    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11854    let (editor, cx) = cx.add_window_view(|window, cx| {
11855        build_editor_with_project(project.clone(), buffer, window, cx)
11856    });
11857
11858    cx.executor().start_waiting();
11859
11860    let fake_server = fake_servers.next().await.unwrap();
11861    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11862        move |_params, _| async move {
11863            Ok(Some(vec![lsp::TextEdit::new(
11864                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11865                "applied-formatting\n".to_string(),
11866            )]))
11867        },
11868    );
11869    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
11870        move |params, _| async move {
11871            assert_eq!(
11872                params.context.only,
11873                Some(vec!["code-action-1".into(), "code-action-2".into()])
11874            );
11875            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
11876            Ok(Some(vec![
11877                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
11878                    kind: Some("code-action-1".into()),
11879                    edit: Some(lsp::WorkspaceEdit::new(
11880                        [(
11881                            uri.clone(),
11882                            vec![lsp::TextEdit::new(
11883                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11884                                "applied-code-action-1-edit\n".to_string(),
11885                            )],
11886                        )]
11887                        .into_iter()
11888                        .collect(),
11889                    )),
11890                    command: Some(lsp::Command {
11891                        command: "the-command-for-code-action-1".into(),
11892                        ..Default::default()
11893                    }),
11894                    ..Default::default()
11895                }),
11896                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
11897                    kind: Some("code-action-2".into()),
11898                    edit: Some(lsp::WorkspaceEdit::new(
11899                        [(
11900                            uri,
11901                            vec![lsp::TextEdit::new(
11902                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11903                                "applied-code-action-2-edit\n".to_string(),
11904                            )],
11905                        )]
11906                        .into_iter()
11907                        .collect(),
11908                    )),
11909                    ..Default::default()
11910                }),
11911            ]))
11912        },
11913    );
11914
11915    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
11916        move |params, _| async move { Ok(params) }
11917    });
11918
11919    let command_lock = Arc::new(futures::lock::Mutex::new(()));
11920    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
11921        let fake = fake_server.clone();
11922        let lock = command_lock.clone();
11923        move |params, _| {
11924            assert_eq!(params.command, "the-command-for-code-action-1");
11925            let fake = fake.clone();
11926            let lock = lock.clone();
11927            async move {
11928                lock.lock().await;
11929                fake.server
11930                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
11931                        label: None,
11932                        edit: lsp::WorkspaceEdit {
11933                            changes: Some(
11934                                [(
11935                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
11936                                    vec![lsp::TextEdit {
11937                                        range: lsp::Range::new(
11938                                            lsp::Position::new(0, 0),
11939                                            lsp::Position::new(0, 0),
11940                                        ),
11941                                        new_text: "applied-code-action-1-command\n".into(),
11942                                    }],
11943                                )]
11944                                .into_iter()
11945                                .collect(),
11946                            ),
11947                            ..Default::default()
11948                        },
11949                    })
11950                    .await
11951                    .into_response()
11952                    .unwrap();
11953                Ok(Some(json!(null)))
11954            }
11955        }
11956    });
11957
11958    cx.executor().start_waiting();
11959    editor
11960        .update_in(cx, |editor, window, cx| {
11961            editor.perform_format(
11962                project.clone(),
11963                FormatTrigger::Manual,
11964                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11965                window,
11966                cx,
11967            )
11968        })
11969        .unwrap()
11970        .await;
11971    editor.update(cx, |editor, cx| {
11972        assert_eq!(
11973            editor.text(cx),
11974            r#"
11975                applied-code-action-2-edit
11976                applied-code-action-1-command
11977                applied-code-action-1-edit
11978                applied-formatting
11979                one
11980                two
11981                three
11982            "#
11983            .unindent()
11984        );
11985    });
11986
11987    editor.update_in(cx, |editor, window, cx| {
11988        editor.undo(&Default::default(), window, cx);
11989        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
11990    });
11991
11992    // Perform a manual edit while waiting for an LSP command
11993    // that's being run as part of a formatting code action.
11994    let lock_guard = command_lock.lock().await;
11995    let format = editor
11996        .update_in(cx, |editor, window, cx| {
11997            editor.perform_format(
11998                project.clone(),
11999                FormatTrigger::Manual,
12000                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12001                window,
12002                cx,
12003            )
12004        })
12005        .unwrap();
12006    cx.run_until_parked();
12007    editor.update(cx, |editor, cx| {
12008        assert_eq!(
12009            editor.text(cx),
12010            r#"
12011                applied-code-action-1-edit
12012                applied-formatting
12013                one
12014                two
12015                three
12016            "#
12017            .unindent()
12018        );
12019
12020        editor.buffer.update(cx, |buffer, cx| {
12021            let ix = buffer.len(cx);
12022            buffer.edit([(ix..ix, "edited\n")], None, cx);
12023        });
12024    });
12025
12026    // Allow the LSP command to proceed. Because the buffer was edited,
12027    // the second code action will not be run.
12028    drop(lock_guard);
12029    format.await;
12030    editor.update_in(cx, |editor, window, cx| {
12031        assert_eq!(
12032            editor.text(cx),
12033            r#"
12034                applied-code-action-1-command
12035                applied-code-action-1-edit
12036                applied-formatting
12037                one
12038                two
12039                three
12040                edited
12041            "#
12042            .unindent()
12043        );
12044
12045        // The manual edit is undone first, because it is the last thing the user did
12046        // (even though the command completed afterwards).
12047        editor.undo(&Default::default(), window, cx);
12048        assert_eq!(
12049            editor.text(cx),
12050            r#"
12051                applied-code-action-1-command
12052                applied-code-action-1-edit
12053                applied-formatting
12054                one
12055                two
12056                three
12057            "#
12058            .unindent()
12059        );
12060
12061        // All the formatting (including the command, which completed after the manual edit)
12062        // is undone together.
12063        editor.undo(&Default::default(), window, cx);
12064        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12065    });
12066}
12067
12068#[gpui::test]
12069async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12070    init_test(cx, |settings| {
12071        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
12072            Formatter::LanguageServer { name: None },
12073        ])))
12074    });
12075
12076    let fs = FakeFs::new(cx.executor());
12077    fs.insert_file(path!("/file.ts"), Default::default()).await;
12078
12079    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12080
12081    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12082    language_registry.add(Arc::new(Language::new(
12083        LanguageConfig {
12084            name: "TypeScript".into(),
12085            matcher: LanguageMatcher {
12086                path_suffixes: vec!["ts".to_string()],
12087                ..Default::default()
12088            },
12089            ..LanguageConfig::default()
12090        },
12091        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12092    )));
12093    update_test_language_settings(cx, |settings| {
12094        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12095    });
12096    let mut fake_servers = language_registry.register_fake_lsp(
12097        "TypeScript",
12098        FakeLspAdapter {
12099            capabilities: lsp::ServerCapabilities {
12100                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12101                ..Default::default()
12102            },
12103            ..Default::default()
12104        },
12105    );
12106
12107    let buffer = project
12108        .update(cx, |project, cx| {
12109            project.open_local_buffer(path!("/file.ts"), cx)
12110        })
12111        .await
12112        .unwrap();
12113
12114    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12115    let (editor, cx) = cx.add_window_view(|window, cx| {
12116        build_editor_with_project(project.clone(), buffer, window, cx)
12117    });
12118    editor.update_in(cx, |editor, window, cx| {
12119        editor.set_text(
12120            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12121            window,
12122            cx,
12123        )
12124    });
12125
12126    cx.executor().start_waiting();
12127    let fake_server = fake_servers.next().await.unwrap();
12128
12129    let format = editor
12130        .update_in(cx, |editor, window, cx| {
12131            editor.perform_code_action_kind(
12132                project.clone(),
12133                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12134                window,
12135                cx,
12136            )
12137        })
12138        .unwrap();
12139    fake_server
12140        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12141            assert_eq!(
12142                params.text_document.uri,
12143                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12144            );
12145            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12146                lsp::CodeAction {
12147                    title: "Organize Imports".to_string(),
12148                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12149                    edit: Some(lsp::WorkspaceEdit {
12150                        changes: Some(
12151                            [(
12152                                params.text_document.uri.clone(),
12153                                vec![lsp::TextEdit::new(
12154                                    lsp::Range::new(
12155                                        lsp::Position::new(1, 0),
12156                                        lsp::Position::new(2, 0),
12157                                    ),
12158                                    "".to_string(),
12159                                )],
12160                            )]
12161                            .into_iter()
12162                            .collect(),
12163                        ),
12164                        ..Default::default()
12165                    }),
12166                    ..Default::default()
12167                },
12168            )]))
12169        })
12170        .next()
12171        .await;
12172    cx.executor().start_waiting();
12173    format.await;
12174    assert_eq!(
12175        editor.update(cx, |editor, cx| editor.text(cx)),
12176        "import { a } from 'module';\n\nconst x = a;\n"
12177    );
12178
12179    editor.update_in(cx, |editor, window, cx| {
12180        editor.set_text(
12181            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12182            window,
12183            cx,
12184        )
12185    });
12186    // Ensure we don't lock if code action hangs.
12187    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12188        move |params, _| async move {
12189            assert_eq!(
12190                params.text_document.uri,
12191                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12192            );
12193            futures::future::pending::<()>().await;
12194            unreachable!()
12195        },
12196    );
12197    let format = editor
12198        .update_in(cx, |editor, window, cx| {
12199            editor.perform_code_action_kind(
12200                project,
12201                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12202                window,
12203                cx,
12204            )
12205        })
12206        .unwrap();
12207    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
12208    cx.executor().start_waiting();
12209    format.await;
12210    assert_eq!(
12211        editor.update(cx, |editor, cx| editor.text(cx)),
12212        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
12213    );
12214}
12215
12216#[gpui::test]
12217async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
12218    init_test(cx, |_| {});
12219
12220    let mut cx = EditorLspTestContext::new_rust(
12221        lsp::ServerCapabilities {
12222            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12223            ..Default::default()
12224        },
12225        cx,
12226    )
12227    .await;
12228
12229    cx.set_state(indoc! {"
12230        one.twoˇ
12231    "});
12232
12233    // The format request takes a long time. When it completes, it inserts
12234    // a newline and an indent before the `.`
12235    cx.lsp
12236        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12237            let executor = cx.background_executor().clone();
12238            async move {
12239                executor.timer(Duration::from_millis(100)).await;
12240                Ok(Some(vec![lsp::TextEdit {
12241                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12242                    new_text: "\n    ".into(),
12243                }]))
12244            }
12245        });
12246
12247    // Submit a format request.
12248    let format_1 = cx
12249        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12250        .unwrap();
12251    cx.executor().run_until_parked();
12252
12253    // Submit a second format request.
12254    let format_2 = cx
12255        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12256        .unwrap();
12257    cx.executor().run_until_parked();
12258
12259    // Wait for both format requests to complete
12260    cx.executor().advance_clock(Duration::from_millis(200));
12261    cx.executor().start_waiting();
12262    format_1.await.unwrap();
12263    cx.executor().start_waiting();
12264    format_2.await.unwrap();
12265
12266    // The formatting edits only happens once.
12267    cx.assert_editor_state(indoc! {"
12268        one
12269            .twoˇ
12270    "});
12271}
12272
12273#[gpui::test]
12274async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12275    init_test(cx, |settings| {
12276        settings.defaults.formatter = Some(SelectedFormatter::Auto)
12277    });
12278
12279    let mut cx = EditorLspTestContext::new_rust(
12280        lsp::ServerCapabilities {
12281            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12282            ..Default::default()
12283        },
12284        cx,
12285    )
12286    .await;
12287
12288    // Set up a buffer white some trailing whitespace and no trailing newline.
12289    cx.set_state(
12290        &[
12291            "one ",   //
12292            "twoˇ",   //
12293            "three ", //
12294            "four",   //
12295        ]
12296        .join("\n"),
12297    );
12298
12299    // Submit a format request.
12300    let format = cx
12301        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12302        .unwrap();
12303
12304    // Record which buffer changes have been sent to the language server
12305    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12306    cx.lsp
12307        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12308            let buffer_changes = buffer_changes.clone();
12309            move |params, _| {
12310                buffer_changes.lock().extend(
12311                    params
12312                        .content_changes
12313                        .into_iter()
12314                        .map(|e| (e.range.unwrap(), e.text)),
12315                );
12316            }
12317        });
12318
12319    // Handle formatting requests to the language server.
12320    cx.lsp
12321        .set_request_handler::<lsp::request::Formatting, _, _>({
12322            let buffer_changes = buffer_changes.clone();
12323            move |_, _| {
12324                // When formatting is requested, trailing whitespace has already been stripped,
12325                // and the trailing newline has already been added.
12326                assert_eq!(
12327                    &buffer_changes.lock()[1..],
12328                    &[
12329                        (
12330                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12331                            "".into()
12332                        ),
12333                        (
12334                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12335                            "".into()
12336                        ),
12337                        (
12338                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12339                            "\n".into()
12340                        ),
12341                    ]
12342                );
12343
12344                // Insert blank lines between each line of the buffer.
12345                async move {
12346                    Ok(Some(vec![
12347                        lsp::TextEdit {
12348                            range: lsp::Range::new(
12349                                lsp::Position::new(1, 0),
12350                                lsp::Position::new(1, 0),
12351                            ),
12352                            new_text: "\n".into(),
12353                        },
12354                        lsp::TextEdit {
12355                            range: lsp::Range::new(
12356                                lsp::Position::new(2, 0),
12357                                lsp::Position::new(2, 0),
12358                            ),
12359                            new_text: "\n".into(),
12360                        },
12361                    ]))
12362                }
12363            }
12364        });
12365
12366    // After formatting the buffer, the trailing whitespace is stripped,
12367    // a newline is appended, and the edits provided by the language server
12368    // have been applied.
12369    format.await.unwrap();
12370    cx.assert_editor_state(
12371        &[
12372            "one",   //
12373            "",      //
12374            "twoˇ",  //
12375            "",      //
12376            "three", //
12377            "four",  //
12378            "",      //
12379        ]
12380        .join("\n"),
12381    );
12382
12383    // Undoing the formatting undoes the trailing whitespace removal, the
12384    // trailing newline, and the LSP edits.
12385    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12386    cx.assert_editor_state(
12387        &[
12388            "one ",   //
12389            "twoˇ",   //
12390            "three ", //
12391            "four",   //
12392        ]
12393        .join("\n"),
12394    );
12395}
12396
12397#[gpui::test]
12398async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12399    cx: &mut TestAppContext,
12400) {
12401    init_test(cx, |_| {});
12402
12403    cx.update(|cx| {
12404        cx.update_global::<SettingsStore, _>(|settings, cx| {
12405            settings.update_user_settings(cx, |settings| {
12406                settings.editor.auto_signature_help = Some(true);
12407            });
12408        });
12409    });
12410
12411    let mut cx = EditorLspTestContext::new_rust(
12412        lsp::ServerCapabilities {
12413            signature_help_provider: Some(lsp::SignatureHelpOptions {
12414                ..Default::default()
12415            }),
12416            ..Default::default()
12417        },
12418        cx,
12419    )
12420    .await;
12421
12422    let language = Language::new(
12423        LanguageConfig {
12424            name: "Rust".into(),
12425            brackets: BracketPairConfig {
12426                pairs: vec![
12427                    BracketPair {
12428                        start: "{".to_string(),
12429                        end: "}".to_string(),
12430                        close: true,
12431                        surround: true,
12432                        newline: true,
12433                    },
12434                    BracketPair {
12435                        start: "(".to_string(),
12436                        end: ")".to_string(),
12437                        close: true,
12438                        surround: true,
12439                        newline: true,
12440                    },
12441                    BracketPair {
12442                        start: "/*".to_string(),
12443                        end: " */".to_string(),
12444                        close: true,
12445                        surround: true,
12446                        newline: true,
12447                    },
12448                    BracketPair {
12449                        start: "[".to_string(),
12450                        end: "]".to_string(),
12451                        close: false,
12452                        surround: false,
12453                        newline: true,
12454                    },
12455                    BracketPair {
12456                        start: "\"".to_string(),
12457                        end: "\"".to_string(),
12458                        close: true,
12459                        surround: true,
12460                        newline: false,
12461                    },
12462                    BracketPair {
12463                        start: "<".to_string(),
12464                        end: ">".to_string(),
12465                        close: false,
12466                        surround: true,
12467                        newline: true,
12468                    },
12469                ],
12470                ..Default::default()
12471            },
12472            autoclose_before: "})]".to_string(),
12473            ..Default::default()
12474        },
12475        Some(tree_sitter_rust::LANGUAGE.into()),
12476    );
12477    let language = Arc::new(language);
12478
12479    cx.language_registry().add(language.clone());
12480    cx.update_buffer(|buffer, cx| {
12481        buffer.set_language(Some(language), cx);
12482    });
12483
12484    cx.set_state(
12485        &r#"
12486            fn main() {
12487                sampleˇ
12488            }
12489        "#
12490        .unindent(),
12491    );
12492
12493    cx.update_editor(|editor, window, cx| {
12494        editor.handle_input("(", window, cx);
12495    });
12496    cx.assert_editor_state(
12497        &"
12498            fn main() {
12499                sample(ˇ)
12500            }
12501        "
12502        .unindent(),
12503    );
12504
12505    let mocked_response = lsp::SignatureHelp {
12506        signatures: vec![lsp::SignatureInformation {
12507            label: "fn sample(param1: u8, param2: u8)".to_string(),
12508            documentation: None,
12509            parameters: Some(vec![
12510                lsp::ParameterInformation {
12511                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12512                    documentation: None,
12513                },
12514                lsp::ParameterInformation {
12515                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12516                    documentation: None,
12517                },
12518            ]),
12519            active_parameter: None,
12520        }],
12521        active_signature: Some(0),
12522        active_parameter: Some(0),
12523    };
12524    handle_signature_help_request(&mut cx, mocked_response).await;
12525
12526    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12527        .await;
12528
12529    cx.editor(|editor, _, _| {
12530        let signature_help_state = editor.signature_help_state.popover().cloned();
12531        let signature = signature_help_state.unwrap();
12532        assert_eq!(
12533            signature.signatures[signature.current_signature].label,
12534            "fn sample(param1: u8, param2: u8)"
12535        );
12536    });
12537}
12538
12539#[gpui::test]
12540async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
12541    init_test(cx, |_| {});
12542
12543    cx.update(|cx| {
12544        cx.update_global::<SettingsStore, _>(|settings, cx| {
12545            settings.update_user_settings(cx, |settings| {
12546                settings.editor.auto_signature_help = Some(false);
12547                settings.editor.show_signature_help_after_edits = Some(false);
12548            });
12549        });
12550    });
12551
12552    let mut cx = EditorLspTestContext::new_rust(
12553        lsp::ServerCapabilities {
12554            signature_help_provider: Some(lsp::SignatureHelpOptions {
12555                ..Default::default()
12556            }),
12557            ..Default::default()
12558        },
12559        cx,
12560    )
12561    .await;
12562
12563    let language = Language::new(
12564        LanguageConfig {
12565            name: "Rust".into(),
12566            brackets: BracketPairConfig {
12567                pairs: vec![
12568                    BracketPair {
12569                        start: "{".to_string(),
12570                        end: "}".to_string(),
12571                        close: true,
12572                        surround: true,
12573                        newline: true,
12574                    },
12575                    BracketPair {
12576                        start: "(".to_string(),
12577                        end: ")".to_string(),
12578                        close: true,
12579                        surround: true,
12580                        newline: true,
12581                    },
12582                    BracketPair {
12583                        start: "/*".to_string(),
12584                        end: " */".to_string(),
12585                        close: true,
12586                        surround: true,
12587                        newline: true,
12588                    },
12589                    BracketPair {
12590                        start: "[".to_string(),
12591                        end: "]".to_string(),
12592                        close: false,
12593                        surround: false,
12594                        newline: true,
12595                    },
12596                    BracketPair {
12597                        start: "\"".to_string(),
12598                        end: "\"".to_string(),
12599                        close: true,
12600                        surround: true,
12601                        newline: false,
12602                    },
12603                    BracketPair {
12604                        start: "<".to_string(),
12605                        end: ">".to_string(),
12606                        close: false,
12607                        surround: true,
12608                        newline: true,
12609                    },
12610                ],
12611                ..Default::default()
12612            },
12613            autoclose_before: "})]".to_string(),
12614            ..Default::default()
12615        },
12616        Some(tree_sitter_rust::LANGUAGE.into()),
12617    );
12618    let language = Arc::new(language);
12619
12620    cx.language_registry().add(language.clone());
12621    cx.update_buffer(|buffer, cx| {
12622        buffer.set_language(Some(language), cx);
12623    });
12624
12625    // Ensure that signature_help is not called when no signature help is enabled.
12626    cx.set_state(
12627        &r#"
12628            fn main() {
12629                sampleˇ
12630            }
12631        "#
12632        .unindent(),
12633    );
12634    cx.update_editor(|editor, window, cx| {
12635        editor.handle_input("(", window, cx);
12636    });
12637    cx.assert_editor_state(
12638        &"
12639            fn main() {
12640                sample(ˇ)
12641            }
12642        "
12643        .unindent(),
12644    );
12645    cx.editor(|editor, _, _| {
12646        assert!(editor.signature_help_state.task().is_none());
12647    });
12648
12649    let mocked_response = lsp::SignatureHelp {
12650        signatures: vec![lsp::SignatureInformation {
12651            label: "fn sample(param1: u8, param2: u8)".to_string(),
12652            documentation: None,
12653            parameters: Some(vec![
12654                lsp::ParameterInformation {
12655                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12656                    documentation: None,
12657                },
12658                lsp::ParameterInformation {
12659                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12660                    documentation: None,
12661                },
12662            ]),
12663            active_parameter: None,
12664        }],
12665        active_signature: Some(0),
12666        active_parameter: Some(0),
12667    };
12668
12669    // Ensure that signature_help is called when enabled afte edits
12670    cx.update(|_, cx| {
12671        cx.update_global::<SettingsStore, _>(|settings, cx| {
12672            settings.update_user_settings(cx, |settings| {
12673                settings.editor.auto_signature_help = Some(false);
12674                settings.editor.show_signature_help_after_edits = Some(true);
12675            });
12676        });
12677    });
12678    cx.set_state(
12679        &r#"
12680            fn main() {
12681                sampleˇ
12682            }
12683        "#
12684        .unindent(),
12685    );
12686    cx.update_editor(|editor, window, cx| {
12687        editor.handle_input("(", window, cx);
12688    });
12689    cx.assert_editor_state(
12690        &"
12691            fn main() {
12692                sample(ˇ)
12693            }
12694        "
12695        .unindent(),
12696    );
12697    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12698    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12699        .await;
12700    cx.update_editor(|editor, _, _| {
12701        let signature_help_state = editor.signature_help_state.popover().cloned();
12702        assert!(signature_help_state.is_some());
12703        let signature = signature_help_state.unwrap();
12704        assert_eq!(
12705            signature.signatures[signature.current_signature].label,
12706            "fn sample(param1: u8, param2: u8)"
12707        );
12708        editor.signature_help_state = SignatureHelpState::default();
12709    });
12710
12711    // Ensure that signature_help is called when auto signature help override is enabled
12712    cx.update(|_, cx| {
12713        cx.update_global::<SettingsStore, _>(|settings, cx| {
12714            settings.update_user_settings(cx, |settings| {
12715                settings.editor.auto_signature_help = Some(true);
12716                settings.editor.show_signature_help_after_edits = Some(false);
12717            });
12718        });
12719    });
12720    cx.set_state(
12721        &r#"
12722            fn main() {
12723                sampleˇ
12724            }
12725        "#
12726        .unindent(),
12727    );
12728    cx.update_editor(|editor, window, cx| {
12729        editor.handle_input("(", window, cx);
12730    });
12731    cx.assert_editor_state(
12732        &"
12733            fn main() {
12734                sample(ˇ)
12735            }
12736        "
12737        .unindent(),
12738    );
12739    handle_signature_help_request(&mut cx, mocked_response).await;
12740    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12741        .await;
12742    cx.editor(|editor, _, _| {
12743        let signature_help_state = editor.signature_help_state.popover().cloned();
12744        assert!(signature_help_state.is_some());
12745        let signature = signature_help_state.unwrap();
12746        assert_eq!(
12747            signature.signatures[signature.current_signature].label,
12748            "fn sample(param1: u8, param2: u8)"
12749        );
12750    });
12751}
12752
12753#[gpui::test]
12754async fn test_signature_help(cx: &mut TestAppContext) {
12755    init_test(cx, |_| {});
12756    cx.update(|cx| {
12757        cx.update_global::<SettingsStore, _>(|settings, cx| {
12758            settings.update_user_settings(cx, |settings| {
12759                settings.editor.auto_signature_help = Some(true);
12760            });
12761        });
12762    });
12763
12764    let mut cx = EditorLspTestContext::new_rust(
12765        lsp::ServerCapabilities {
12766            signature_help_provider: Some(lsp::SignatureHelpOptions {
12767                ..Default::default()
12768            }),
12769            ..Default::default()
12770        },
12771        cx,
12772    )
12773    .await;
12774
12775    // A test that directly calls `show_signature_help`
12776    cx.update_editor(|editor, window, cx| {
12777        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12778    });
12779
12780    let mocked_response = lsp::SignatureHelp {
12781        signatures: vec![lsp::SignatureInformation {
12782            label: "fn sample(param1: u8, param2: u8)".to_string(),
12783            documentation: None,
12784            parameters: Some(vec![
12785                lsp::ParameterInformation {
12786                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12787                    documentation: None,
12788                },
12789                lsp::ParameterInformation {
12790                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12791                    documentation: None,
12792                },
12793            ]),
12794            active_parameter: None,
12795        }],
12796        active_signature: Some(0),
12797        active_parameter: Some(0),
12798    };
12799    handle_signature_help_request(&mut cx, mocked_response).await;
12800
12801    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12802        .await;
12803
12804    cx.editor(|editor, _, _| {
12805        let signature_help_state = editor.signature_help_state.popover().cloned();
12806        assert!(signature_help_state.is_some());
12807        let signature = signature_help_state.unwrap();
12808        assert_eq!(
12809            signature.signatures[signature.current_signature].label,
12810            "fn sample(param1: u8, param2: u8)"
12811        );
12812    });
12813
12814    // When exiting outside from inside the brackets, `signature_help` is closed.
12815    cx.set_state(indoc! {"
12816        fn main() {
12817            sample(ˇ);
12818        }
12819
12820        fn sample(param1: u8, param2: u8) {}
12821    "});
12822
12823    cx.update_editor(|editor, window, cx| {
12824        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12825            s.select_ranges([0..0])
12826        });
12827    });
12828
12829    let mocked_response = lsp::SignatureHelp {
12830        signatures: Vec::new(),
12831        active_signature: None,
12832        active_parameter: None,
12833    };
12834    handle_signature_help_request(&mut cx, mocked_response).await;
12835
12836    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
12837        .await;
12838
12839    cx.editor(|editor, _, _| {
12840        assert!(!editor.signature_help_state.is_shown());
12841    });
12842
12843    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
12844    cx.set_state(indoc! {"
12845        fn main() {
12846            sample(ˇ);
12847        }
12848
12849        fn sample(param1: u8, param2: u8) {}
12850    "});
12851
12852    let mocked_response = lsp::SignatureHelp {
12853        signatures: vec![lsp::SignatureInformation {
12854            label: "fn sample(param1: u8, param2: u8)".to_string(),
12855            documentation: None,
12856            parameters: Some(vec![
12857                lsp::ParameterInformation {
12858                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12859                    documentation: None,
12860                },
12861                lsp::ParameterInformation {
12862                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12863                    documentation: None,
12864                },
12865            ]),
12866            active_parameter: None,
12867        }],
12868        active_signature: Some(0),
12869        active_parameter: Some(0),
12870    };
12871    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12872    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12873        .await;
12874    cx.editor(|editor, _, _| {
12875        assert!(editor.signature_help_state.is_shown());
12876    });
12877
12878    // Restore the popover with more parameter input
12879    cx.set_state(indoc! {"
12880        fn main() {
12881            sample(param1, param2ˇ);
12882        }
12883
12884        fn sample(param1: u8, param2: u8) {}
12885    "});
12886
12887    let mocked_response = lsp::SignatureHelp {
12888        signatures: vec![lsp::SignatureInformation {
12889            label: "fn sample(param1: u8, param2: u8)".to_string(),
12890            documentation: None,
12891            parameters: Some(vec![
12892                lsp::ParameterInformation {
12893                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12894                    documentation: None,
12895                },
12896                lsp::ParameterInformation {
12897                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12898                    documentation: None,
12899                },
12900            ]),
12901            active_parameter: None,
12902        }],
12903        active_signature: Some(0),
12904        active_parameter: Some(1),
12905    };
12906    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12907    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12908        .await;
12909
12910    // When selecting a range, the popover is gone.
12911    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
12912    cx.update_editor(|editor, window, cx| {
12913        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12914            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
12915        })
12916    });
12917    cx.assert_editor_state(indoc! {"
12918        fn main() {
12919            sample(param1, «ˇparam2»);
12920        }
12921
12922        fn sample(param1: u8, param2: u8) {}
12923    "});
12924    cx.editor(|editor, _, _| {
12925        assert!(!editor.signature_help_state.is_shown());
12926    });
12927
12928    // When unselecting again, the popover is back if within the brackets.
12929    cx.update_editor(|editor, window, cx| {
12930        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12931            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
12932        })
12933    });
12934    cx.assert_editor_state(indoc! {"
12935        fn main() {
12936            sample(param1, ˇparam2);
12937        }
12938
12939        fn sample(param1: u8, param2: u8) {}
12940    "});
12941    handle_signature_help_request(&mut cx, mocked_response).await;
12942    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12943        .await;
12944    cx.editor(|editor, _, _| {
12945        assert!(editor.signature_help_state.is_shown());
12946    });
12947
12948    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
12949    cx.update_editor(|editor, window, cx| {
12950        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12951            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
12952            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
12953        })
12954    });
12955    cx.assert_editor_state(indoc! {"
12956        fn main() {
12957            sample(param1, ˇparam2);
12958        }
12959
12960        fn sample(param1: u8, param2: u8) {}
12961    "});
12962
12963    let mocked_response = lsp::SignatureHelp {
12964        signatures: vec![lsp::SignatureInformation {
12965            label: "fn sample(param1: u8, param2: u8)".to_string(),
12966            documentation: None,
12967            parameters: Some(vec![
12968                lsp::ParameterInformation {
12969                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12970                    documentation: None,
12971                },
12972                lsp::ParameterInformation {
12973                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12974                    documentation: None,
12975                },
12976            ]),
12977            active_parameter: None,
12978        }],
12979        active_signature: Some(0),
12980        active_parameter: Some(1),
12981    };
12982    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12983    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12984        .await;
12985    cx.update_editor(|editor, _, cx| {
12986        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
12987    });
12988    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
12989        .await;
12990    cx.update_editor(|editor, window, cx| {
12991        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12992            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
12993        })
12994    });
12995    cx.assert_editor_state(indoc! {"
12996        fn main() {
12997            sample(param1, «ˇparam2»);
12998        }
12999
13000        fn sample(param1: u8, param2: u8) {}
13001    "});
13002    cx.update_editor(|editor, window, cx| {
13003        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13004            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13005        })
13006    });
13007    cx.assert_editor_state(indoc! {"
13008        fn main() {
13009            sample(param1, ˇparam2);
13010        }
13011
13012        fn sample(param1: u8, param2: u8) {}
13013    "});
13014    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13015        .await;
13016}
13017
13018#[gpui::test]
13019async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13020    init_test(cx, |_| {});
13021
13022    let mut cx = EditorLspTestContext::new_rust(
13023        lsp::ServerCapabilities {
13024            signature_help_provider: Some(lsp::SignatureHelpOptions {
13025                ..Default::default()
13026            }),
13027            ..Default::default()
13028        },
13029        cx,
13030    )
13031    .await;
13032
13033    cx.set_state(indoc! {"
13034        fn main() {
13035            overloadedˇ
13036        }
13037    "});
13038
13039    cx.update_editor(|editor, window, cx| {
13040        editor.handle_input("(", window, cx);
13041        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13042    });
13043
13044    // Mock response with 3 signatures
13045    let mocked_response = lsp::SignatureHelp {
13046        signatures: vec![
13047            lsp::SignatureInformation {
13048                label: "fn overloaded(x: i32)".to_string(),
13049                documentation: None,
13050                parameters: Some(vec![lsp::ParameterInformation {
13051                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13052                    documentation: None,
13053                }]),
13054                active_parameter: None,
13055            },
13056            lsp::SignatureInformation {
13057                label: "fn overloaded(x: i32, y: i32)".to_string(),
13058                documentation: None,
13059                parameters: Some(vec![
13060                    lsp::ParameterInformation {
13061                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13062                        documentation: None,
13063                    },
13064                    lsp::ParameterInformation {
13065                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13066                        documentation: None,
13067                    },
13068                ]),
13069                active_parameter: None,
13070            },
13071            lsp::SignatureInformation {
13072                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13073                documentation: None,
13074                parameters: Some(vec![
13075                    lsp::ParameterInformation {
13076                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13077                        documentation: None,
13078                    },
13079                    lsp::ParameterInformation {
13080                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13081                        documentation: None,
13082                    },
13083                    lsp::ParameterInformation {
13084                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13085                        documentation: None,
13086                    },
13087                ]),
13088                active_parameter: None,
13089            },
13090        ],
13091        active_signature: Some(1),
13092        active_parameter: Some(0),
13093    };
13094    handle_signature_help_request(&mut cx, mocked_response).await;
13095
13096    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13097        .await;
13098
13099    // Verify we have multiple signatures and the right one is selected
13100    cx.editor(|editor, _, _| {
13101        let popover = editor.signature_help_state.popover().cloned().unwrap();
13102        assert_eq!(popover.signatures.len(), 3);
13103        // active_signature was 1, so that should be the current
13104        assert_eq!(popover.current_signature, 1);
13105        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13106        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13107        assert_eq!(
13108            popover.signatures[2].label,
13109            "fn overloaded(x: i32, y: i32, z: i32)"
13110        );
13111    });
13112
13113    // Test navigation functionality
13114    cx.update_editor(|editor, window, cx| {
13115        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13116    });
13117
13118    cx.editor(|editor, _, _| {
13119        let popover = editor.signature_help_state.popover().cloned().unwrap();
13120        assert_eq!(popover.current_signature, 2);
13121    });
13122
13123    // Test wrap around
13124    cx.update_editor(|editor, window, cx| {
13125        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13126    });
13127
13128    cx.editor(|editor, _, _| {
13129        let popover = editor.signature_help_state.popover().cloned().unwrap();
13130        assert_eq!(popover.current_signature, 0);
13131    });
13132
13133    // Test previous navigation
13134    cx.update_editor(|editor, window, cx| {
13135        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13136    });
13137
13138    cx.editor(|editor, _, _| {
13139        let popover = editor.signature_help_state.popover().cloned().unwrap();
13140        assert_eq!(popover.current_signature, 2);
13141    });
13142}
13143
13144#[gpui::test]
13145async fn test_completion_mode(cx: &mut TestAppContext) {
13146    init_test(cx, |_| {});
13147    let mut cx = EditorLspTestContext::new_rust(
13148        lsp::ServerCapabilities {
13149            completion_provider: Some(lsp::CompletionOptions {
13150                resolve_provider: Some(true),
13151                ..Default::default()
13152            }),
13153            ..Default::default()
13154        },
13155        cx,
13156    )
13157    .await;
13158
13159    struct Run {
13160        run_description: &'static str,
13161        initial_state: String,
13162        buffer_marked_text: String,
13163        completion_label: &'static str,
13164        completion_text: &'static str,
13165        expected_with_insert_mode: String,
13166        expected_with_replace_mode: String,
13167        expected_with_replace_subsequence_mode: String,
13168        expected_with_replace_suffix_mode: String,
13169    }
13170
13171    let runs = [
13172        Run {
13173            run_description: "Start of word matches completion text",
13174            initial_state: "before ediˇ after".into(),
13175            buffer_marked_text: "before <edi|> after".into(),
13176            completion_label: "editor",
13177            completion_text: "editor",
13178            expected_with_insert_mode: "before editorˇ after".into(),
13179            expected_with_replace_mode: "before editorˇ after".into(),
13180            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13181            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13182        },
13183        Run {
13184            run_description: "Accept same text at the middle of the word",
13185            initial_state: "before ediˇtor after".into(),
13186            buffer_marked_text: "before <edi|tor> after".into(),
13187            completion_label: "editor",
13188            completion_text: "editor",
13189            expected_with_insert_mode: "before editorˇtor after".into(),
13190            expected_with_replace_mode: "before editorˇ after".into(),
13191            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13192            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13193        },
13194        Run {
13195            run_description: "End of word matches completion text -- cursor at end",
13196            initial_state: "before torˇ after".into(),
13197            buffer_marked_text: "before <tor|> after".into(),
13198            completion_label: "editor",
13199            completion_text: "editor",
13200            expected_with_insert_mode: "before editorˇ after".into(),
13201            expected_with_replace_mode: "before editorˇ after".into(),
13202            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13203            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13204        },
13205        Run {
13206            run_description: "End of word matches completion text -- cursor at start",
13207            initial_state: "before ˇtor after".into(),
13208            buffer_marked_text: "before <|tor> after".into(),
13209            completion_label: "editor",
13210            completion_text: "editor",
13211            expected_with_insert_mode: "before editorˇtor after".into(),
13212            expected_with_replace_mode: "before editorˇ after".into(),
13213            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13214            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13215        },
13216        Run {
13217            run_description: "Prepend text containing whitespace",
13218            initial_state: "pˇfield: bool".into(),
13219            buffer_marked_text: "<p|field>: bool".into(),
13220            completion_label: "pub ",
13221            completion_text: "pub ",
13222            expected_with_insert_mode: "pub ˇfield: bool".into(),
13223            expected_with_replace_mode: "pub ˇ: bool".into(),
13224            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13225            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13226        },
13227        Run {
13228            run_description: "Add element to start of list",
13229            initial_state: "[element_ˇelement_2]".into(),
13230            buffer_marked_text: "[<element_|element_2>]".into(),
13231            completion_label: "element_1",
13232            completion_text: "element_1",
13233            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13234            expected_with_replace_mode: "[element_1ˇ]".into(),
13235            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13236            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13237        },
13238        Run {
13239            run_description: "Add element to start of list -- first and second elements are equal",
13240            initial_state: "[elˇelement]".into(),
13241            buffer_marked_text: "[<el|element>]".into(),
13242            completion_label: "element",
13243            completion_text: "element",
13244            expected_with_insert_mode: "[elementˇelement]".into(),
13245            expected_with_replace_mode: "[elementˇ]".into(),
13246            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13247            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13248        },
13249        Run {
13250            run_description: "Ends with matching suffix",
13251            initial_state: "SubˇError".into(),
13252            buffer_marked_text: "<Sub|Error>".into(),
13253            completion_label: "SubscriptionError",
13254            completion_text: "SubscriptionError",
13255            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13256            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13257            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13258            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13259        },
13260        Run {
13261            run_description: "Suffix is a subsequence -- contiguous",
13262            initial_state: "SubˇErr".into(),
13263            buffer_marked_text: "<Sub|Err>".into(),
13264            completion_label: "SubscriptionError",
13265            completion_text: "SubscriptionError",
13266            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13267            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13268            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13269            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13270        },
13271        Run {
13272            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13273            initial_state: "Suˇscrirr".into(),
13274            buffer_marked_text: "<Su|scrirr>".into(),
13275            completion_label: "SubscriptionError",
13276            completion_text: "SubscriptionError",
13277            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13278            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13279            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13280            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13281        },
13282        Run {
13283            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13284            initial_state: "foo(indˇix)".into(),
13285            buffer_marked_text: "foo(<ind|ix>)".into(),
13286            completion_label: "node_index",
13287            completion_text: "node_index",
13288            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13289            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13290            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13291            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13292        },
13293        Run {
13294            run_description: "Replace range ends before cursor - should extend to cursor",
13295            initial_state: "before editˇo after".into(),
13296            buffer_marked_text: "before <{ed}>it|o after".into(),
13297            completion_label: "editor",
13298            completion_text: "editor",
13299            expected_with_insert_mode: "before editorˇo after".into(),
13300            expected_with_replace_mode: "before editorˇo after".into(),
13301            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13302            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13303        },
13304        Run {
13305            run_description: "Uses label for suffix matching",
13306            initial_state: "before ediˇtor after".into(),
13307            buffer_marked_text: "before <edi|tor> after".into(),
13308            completion_label: "editor",
13309            completion_text: "editor()",
13310            expected_with_insert_mode: "before editor()ˇtor after".into(),
13311            expected_with_replace_mode: "before editor()ˇ after".into(),
13312            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13313            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13314        },
13315        Run {
13316            run_description: "Case insensitive subsequence and suffix matching",
13317            initial_state: "before EDiˇtoR after".into(),
13318            buffer_marked_text: "before <EDi|toR> after".into(),
13319            completion_label: "editor",
13320            completion_text: "editor",
13321            expected_with_insert_mode: "before editorˇtoR after".into(),
13322            expected_with_replace_mode: "before editorˇ after".into(),
13323            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13324            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13325        },
13326    ];
13327
13328    for run in runs {
13329        let run_variations = [
13330            (LspInsertMode::Insert, run.expected_with_insert_mode),
13331            (LspInsertMode::Replace, run.expected_with_replace_mode),
13332            (
13333                LspInsertMode::ReplaceSubsequence,
13334                run.expected_with_replace_subsequence_mode,
13335            ),
13336            (
13337                LspInsertMode::ReplaceSuffix,
13338                run.expected_with_replace_suffix_mode,
13339            ),
13340        ];
13341
13342        for (lsp_insert_mode, expected_text) in run_variations {
13343            eprintln!(
13344                "run = {:?}, mode = {lsp_insert_mode:.?}",
13345                run.run_description,
13346            );
13347
13348            update_test_language_settings(&mut cx, |settings| {
13349                settings.defaults.completions = Some(CompletionSettingsContent {
13350                    lsp_insert_mode: Some(lsp_insert_mode),
13351                    words: Some(WordsCompletionMode::Disabled),
13352                    words_min_length: Some(0),
13353                    ..Default::default()
13354                });
13355            });
13356
13357            cx.set_state(&run.initial_state);
13358            cx.update_editor(|editor, window, cx| {
13359                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13360            });
13361
13362            let counter = Arc::new(AtomicUsize::new(0));
13363            handle_completion_request_with_insert_and_replace(
13364                &mut cx,
13365                &run.buffer_marked_text,
13366                vec![(run.completion_label, run.completion_text)],
13367                counter.clone(),
13368            )
13369            .await;
13370            cx.condition(|editor, _| editor.context_menu_visible())
13371                .await;
13372            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13373
13374            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13375                editor
13376                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13377                    .unwrap()
13378            });
13379            cx.assert_editor_state(&expected_text);
13380            handle_resolve_completion_request(&mut cx, None).await;
13381            apply_additional_edits.await.unwrap();
13382        }
13383    }
13384}
13385
13386#[gpui::test]
13387async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13388    init_test(cx, |_| {});
13389    let mut cx = EditorLspTestContext::new_rust(
13390        lsp::ServerCapabilities {
13391            completion_provider: Some(lsp::CompletionOptions {
13392                resolve_provider: Some(true),
13393                ..Default::default()
13394            }),
13395            ..Default::default()
13396        },
13397        cx,
13398    )
13399    .await;
13400
13401    let initial_state = "SubˇError";
13402    let buffer_marked_text = "<Sub|Error>";
13403    let completion_text = "SubscriptionError";
13404    let expected_with_insert_mode = "SubscriptionErrorˇError";
13405    let expected_with_replace_mode = "SubscriptionErrorˇ";
13406
13407    update_test_language_settings(&mut cx, |settings| {
13408        settings.defaults.completions = Some(CompletionSettingsContent {
13409            words: Some(WordsCompletionMode::Disabled),
13410            words_min_length: Some(0),
13411            // set the opposite here to ensure that the action is overriding the default behavior
13412            lsp_insert_mode: Some(LspInsertMode::Insert),
13413            ..Default::default()
13414        });
13415    });
13416
13417    cx.set_state(initial_state);
13418    cx.update_editor(|editor, window, cx| {
13419        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13420    });
13421
13422    let counter = Arc::new(AtomicUsize::new(0));
13423    handle_completion_request_with_insert_and_replace(
13424        &mut cx,
13425        buffer_marked_text,
13426        vec![(completion_text, completion_text)],
13427        counter.clone(),
13428    )
13429    .await;
13430    cx.condition(|editor, _| editor.context_menu_visible())
13431        .await;
13432    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13433
13434    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13435        editor
13436            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13437            .unwrap()
13438    });
13439    cx.assert_editor_state(expected_with_replace_mode);
13440    handle_resolve_completion_request(&mut cx, None).await;
13441    apply_additional_edits.await.unwrap();
13442
13443    update_test_language_settings(&mut cx, |settings| {
13444        settings.defaults.completions = Some(CompletionSettingsContent {
13445            words: Some(WordsCompletionMode::Disabled),
13446            words_min_length: Some(0),
13447            // set the opposite here to ensure that the action is overriding the default behavior
13448            lsp_insert_mode: Some(LspInsertMode::Replace),
13449            ..Default::default()
13450        });
13451    });
13452
13453    cx.set_state(initial_state);
13454    cx.update_editor(|editor, window, cx| {
13455        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13456    });
13457    handle_completion_request_with_insert_and_replace(
13458        &mut cx,
13459        buffer_marked_text,
13460        vec![(completion_text, completion_text)],
13461        counter.clone(),
13462    )
13463    .await;
13464    cx.condition(|editor, _| editor.context_menu_visible())
13465        .await;
13466    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13467
13468    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13469        editor
13470            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13471            .unwrap()
13472    });
13473    cx.assert_editor_state(expected_with_insert_mode);
13474    handle_resolve_completion_request(&mut cx, None).await;
13475    apply_additional_edits.await.unwrap();
13476}
13477
13478#[gpui::test]
13479async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
13480    init_test(cx, |_| {});
13481    let mut cx = EditorLspTestContext::new_rust(
13482        lsp::ServerCapabilities {
13483            completion_provider: Some(lsp::CompletionOptions {
13484                resolve_provider: Some(true),
13485                ..Default::default()
13486            }),
13487            ..Default::default()
13488        },
13489        cx,
13490    )
13491    .await;
13492
13493    // scenario: surrounding text matches completion text
13494    let completion_text = "to_offset";
13495    let initial_state = indoc! {"
13496        1. buf.to_offˇsuffix
13497        2. buf.to_offˇsuf
13498        3. buf.to_offˇfix
13499        4. buf.to_offˇ
13500        5. into_offˇensive
13501        6. ˇsuffix
13502        7. let ˇ //
13503        8. aaˇzz
13504        9. buf.to_off«zzzzzˇ»suffix
13505        10. buf.«ˇzzzzz»suffix
13506        11. to_off«ˇzzzzz»
13507
13508        buf.to_offˇsuffix  // newest cursor
13509    "};
13510    let completion_marked_buffer = indoc! {"
13511        1. buf.to_offsuffix
13512        2. buf.to_offsuf
13513        3. buf.to_offfix
13514        4. buf.to_off
13515        5. into_offensive
13516        6. suffix
13517        7. let  //
13518        8. aazz
13519        9. buf.to_offzzzzzsuffix
13520        10. buf.zzzzzsuffix
13521        11. to_offzzzzz
13522
13523        buf.<to_off|suffix>  // newest cursor
13524    "};
13525    let expected = indoc! {"
13526        1. buf.to_offsetˇ
13527        2. buf.to_offsetˇsuf
13528        3. buf.to_offsetˇfix
13529        4. buf.to_offsetˇ
13530        5. into_offsetˇensive
13531        6. to_offsetˇsuffix
13532        7. let to_offsetˇ //
13533        8. aato_offsetˇzz
13534        9. buf.to_offsetˇ
13535        10. buf.to_offsetˇsuffix
13536        11. to_offsetˇ
13537
13538        buf.to_offsetˇ  // newest cursor
13539    "};
13540    cx.set_state(initial_state);
13541    cx.update_editor(|editor, window, cx| {
13542        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13543    });
13544    handle_completion_request_with_insert_and_replace(
13545        &mut cx,
13546        completion_marked_buffer,
13547        vec![(completion_text, completion_text)],
13548        Arc::new(AtomicUsize::new(0)),
13549    )
13550    .await;
13551    cx.condition(|editor, _| editor.context_menu_visible())
13552        .await;
13553    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13554        editor
13555            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13556            .unwrap()
13557    });
13558    cx.assert_editor_state(expected);
13559    handle_resolve_completion_request(&mut cx, None).await;
13560    apply_additional_edits.await.unwrap();
13561
13562    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
13563    let completion_text = "foo_and_bar";
13564    let initial_state = indoc! {"
13565        1. ooanbˇ
13566        2. zooanbˇ
13567        3. ooanbˇz
13568        4. zooanbˇz
13569        5. ooanˇ
13570        6. oanbˇ
13571
13572        ooanbˇ
13573    "};
13574    let completion_marked_buffer = indoc! {"
13575        1. ooanb
13576        2. zooanb
13577        3. ooanbz
13578        4. zooanbz
13579        5. ooan
13580        6. oanb
13581
13582        <ooanb|>
13583    "};
13584    let expected = indoc! {"
13585        1. foo_and_barˇ
13586        2. zfoo_and_barˇ
13587        3. foo_and_barˇz
13588        4. zfoo_and_barˇz
13589        5. ooanfoo_and_barˇ
13590        6. oanbfoo_and_barˇ
13591
13592        foo_and_barˇ
13593    "};
13594    cx.set_state(initial_state);
13595    cx.update_editor(|editor, window, cx| {
13596        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13597    });
13598    handle_completion_request_with_insert_and_replace(
13599        &mut cx,
13600        completion_marked_buffer,
13601        vec![(completion_text, completion_text)],
13602        Arc::new(AtomicUsize::new(0)),
13603    )
13604    .await;
13605    cx.condition(|editor, _| editor.context_menu_visible())
13606        .await;
13607    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13608        editor
13609            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13610            .unwrap()
13611    });
13612    cx.assert_editor_state(expected);
13613    handle_resolve_completion_request(&mut cx, None).await;
13614    apply_additional_edits.await.unwrap();
13615
13616    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
13617    // (expects the same as if it was inserted at the end)
13618    let completion_text = "foo_and_bar";
13619    let initial_state = indoc! {"
13620        1. ooˇanb
13621        2. zooˇanb
13622        3. ooˇanbz
13623        4. zooˇanbz
13624
13625        ooˇanb
13626    "};
13627    let completion_marked_buffer = indoc! {"
13628        1. ooanb
13629        2. zooanb
13630        3. ooanbz
13631        4. zooanbz
13632
13633        <oo|anb>
13634    "};
13635    let expected = indoc! {"
13636        1. foo_and_barˇ
13637        2. zfoo_and_barˇ
13638        3. foo_and_barˇz
13639        4. zfoo_and_barˇz
13640
13641        foo_and_barˇ
13642    "};
13643    cx.set_state(initial_state);
13644    cx.update_editor(|editor, window, cx| {
13645        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13646    });
13647    handle_completion_request_with_insert_and_replace(
13648        &mut cx,
13649        completion_marked_buffer,
13650        vec![(completion_text, completion_text)],
13651        Arc::new(AtomicUsize::new(0)),
13652    )
13653    .await;
13654    cx.condition(|editor, _| editor.context_menu_visible())
13655        .await;
13656    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13657        editor
13658            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13659            .unwrap()
13660    });
13661    cx.assert_editor_state(expected);
13662    handle_resolve_completion_request(&mut cx, None).await;
13663    apply_additional_edits.await.unwrap();
13664}
13665
13666// This used to crash
13667#[gpui::test]
13668async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
13669    init_test(cx, |_| {});
13670
13671    let buffer_text = indoc! {"
13672        fn main() {
13673            10.satu;
13674
13675            //
13676            // separate cursors so they open in different excerpts (manually reproducible)
13677            //
13678
13679            10.satu20;
13680        }
13681    "};
13682    let multibuffer_text_with_selections = indoc! {"
13683        fn main() {
13684            10.satuˇ;
13685
13686            //
13687
13688            //
13689
13690            10.satuˇ20;
13691        }
13692    "};
13693    let expected_multibuffer = indoc! {"
13694        fn main() {
13695            10.saturating_sub()ˇ;
13696
13697            //
13698
13699            //
13700
13701            10.saturating_sub()ˇ;
13702        }
13703    "};
13704
13705    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
13706    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
13707
13708    let fs = FakeFs::new(cx.executor());
13709    fs.insert_tree(
13710        path!("/a"),
13711        json!({
13712            "main.rs": buffer_text,
13713        }),
13714    )
13715    .await;
13716
13717    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13718    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13719    language_registry.add(rust_lang());
13720    let mut fake_servers = language_registry.register_fake_lsp(
13721        "Rust",
13722        FakeLspAdapter {
13723            capabilities: lsp::ServerCapabilities {
13724                completion_provider: Some(lsp::CompletionOptions {
13725                    resolve_provider: None,
13726                    ..lsp::CompletionOptions::default()
13727                }),
13728                ..lsp::ServerCapabilities::default()
13729            },
13730            ..FakeLspAdapter::default()
13731        },
13732    );
13733    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13734    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13735    let buffer = project
13736        .update(cx, |project, cx| {
13737            project.open_local_buffer(path!("/a/main.rs"), cx)
13738        })
13739        .await
13740        .unwrap();
13741
13742    let multi_buffer = cx.new(|cx| {
13743        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
13744        multi_buffer.push_excerpts(
13745            buffer.clone(),
13746            [ExcerptRange::new(0..first_excerpt_end)],
13747            cx,
13748        );
13749        multi_buffer.push_excerpts(
13750            buffer.clone(),
13751            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
13752            cx,
13753        );
13754        multi_buffer
13755    });
13756
13757    let editor = workspace
13758        .update(cx, |_, window, cx| {
13759            cx.new(|cx| {
13760                Editor::new(
13761                    EditorMode::Full {
13762                        scale_ui_elements_with_buffer_font_size: false,
13763                        show_active_line_background: false,
13764                        sized_by_content: false,
13765                    },
13766                    multi_buffer.clone(),
13767                    Some(project.clone()),
13768                    window,
13769                    cx,
13770                )
13771            })
13772        })
13773        .unwrap();
13774
13775    let pane = workspace
13776        .update(cx, |workspace, _, _| workspace.active_pane().clone())
13777        .unwrap();
13778    pane.update_in(cx, |pane, window, cx| {
13779        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
13780    });
13781
13782    let fake_server = fake_servers.next().await.unwrap();
13783
13784    editor.update_in(cx, |editor, window, cx| {
13785        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13786            s.select_ranges([
13787                Point::new(1, 11)..Point::new(1, 11),
13788                Point::new(7, 11)..Point::new(7, 11),
13789            ])
13790        });
13791
13792        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
13793    });
13794
13795    editor.update_in(cx, |editor, window, cx| {
13796        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13797    });
13798
13799    fake_server
13800        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13801            let completion_item = lsp::CompletionItem {
13802                label: "saturating_sub()".into(),
13803                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
13804                    lsp::InsertReplaceEdit {
13805                        new_text: "saturating_sub()".to_owned(),
13806                        insert: lsp::Range::new(
13807                            lsp::Position::new(7, 7),
13808                            lsp::Position::new(7, 11),
13809                        ),
13810                        replace: lsp::Range::new(
13811                            lsp::Position::new(7, 7),
13812                            lsp::Position::new(7, 13),
13813                        ),
13814                    },
13815                )),
13816                ..lsp::CompletionItem::default()
13817            };
13818
13819            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
13820        })
13821        .next()
13822        .await
13823        .unwrap();
13824
13825    cx.condition(&editor, |editor, _| editor.context_menu_visible())
13826        .await;
13827
13828    editor
13829        .update_in(cx, |editor, window, cx| {
13830            editor
13831                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13832                .unwrap()
13833        })
13834        .await
13835        .unwrap();
13836
13837    editor.update(cx, |editor, cx| {
13838        assert_text_with_selections(editor, expected_multibuffer, cx);
13839    })
13840}
13841
13842#[gpui::test]
13843async fn test_completion(cx: &mut TestAppContext) {
13844    init_test(cx, |_| {});
13845
13846    let mut cx = EditorLspTestContext::new_rust(
13847        lsp::ServerCapabilities {
13848            completion_provider: Some(lsp::CompletionOptions {
13849                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13850                resolve_provider: Some(true),
13851                ..Default::default()
13852            }),
13853            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13854            ..Default::default()
13855        },
13856        cx,
13857    )
13858    .await;
13859    let counter = Arc::new(AtomicUsize::new(0));
13860
13861    cx.set_state(indoc! {"
13862        oneˇ
13863        two
13864        three
13865    "});
13866    cx.simulate_keystroke(".");
13867    handle_completion_request(
13868        indoc! {"
13869            one.|<>
13870            two
13871            three
13872        "},
13873        vec!["first_completion", "second_completion"],
13874        true,
13875        counter.clone(),
13876        &mut cx,
13877    )
13878    .await;
13879    cx.condition(|editor, _| editor.context_menu_visible())
13880        .await;
13881    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13882
13883    let _handler = handle_signature_help_request(
13884        &mut cx,
13885        lsp::SignatureHelp {
13886            signatures: vec![lsp::SignatureInformation {
13887                label: "test signature".to_string(),
13888                documentation: None,
13889                parameters: Some(vec![lsp::ParameterInformation {
13890                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
13891                    documentation: None,
13892                }]),
13893                active_parameter: None,
13894            }],
13895            active_signature: None,
13896            active_parameter: None,
13897        },
13898    );
13899    cx.update_editor(|editor, window, cx| {
13900        assert!(
13901            !editor.signature_help_state.is_shown(),
13902            "No signature help was called for"
13903        );
13904        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13905    });
13906    cx.run_until_parked();
13907    cx.update_editor(|editor, _, _| {
13908        assert!(
13909            !editor.signature_help_state.is_shown(),
13910            "No signature help should be shown when completions menu is open"
13911        );
13912    });
13913
13914    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13915        editor.context_menu_next(&Default::default(), window, cx);
13916        editor
13917            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13918            .unwrap()
13919    });
13920    cx.assert_editor_state(indoc! {"
13921        one.second_completionˇ
13922        two
13923        three
13924    "});
13925
13926    handle_resolve_completion_request(
13927        &mut cx,
13928        Some(vec![
13929            (
13930                //This overlaps with the primary completion edit which is
13931                //misbehavior from the LSP spec, test that we filter it out
13932                indoc! {"
13933                    one.second_ˇcompletion
13934                    two
13935                    threeˇ
13936                "},
13937                "overlapping additional edit",
13938            ),
13939            (
13940                indoc! {"
13941                    one.second_completion
13942                    two
13943                    threeˇ
13944                "},
13945                "\nadditional edit",
13946            ),
13947        ]),
13948    )
13949    .await;
13950    apply_additional_edits.await.unwrap();
13951    cx.assert_editor_state(indoc! {"
13952        one.second_completionˇ
13953        two
13954        three
13955        additional edit
13956    "});
13957
13958    cx.set_state(indoc! {"
13959        one.second_completion
13960        twoˇ
13961        threeˇ
13962        additional edit
13963    "});
13964    cx.simulate_keystroke(" ");
13965    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13966    cx.simulate_keystroke("s");
13967    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13968
13969    cx.assert_editor_state(indoc! {"
13970        one.second_completion
13971        two sˇ
13972        three sˇ
13973        additional edit
13974    "});
13975    handle_completion_request(
13976        indoc! {"
13977            one.second_completion
13978            two s
13979            three <s|>
13980            additional edit
13981        "},
13982        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
13983        true,
13984        counter.clone(),
13985        &mut cx,
13986    )
13987    .await;
13988    cx.condition(|editor, _| editor.context_menu_visible())
13989        .await;
13990    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13991
13992    cx.simulate_keystroke("i");
13993
13994    handle_completion_request(
13995        indoc! {"
13996            one.second_completion
13997            two si
13998            three <si|>
13999            additional edit
14000        "},
14001        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14002        true,
14003        counter.clone(),
14004        &mut cx,
14005    )
14006    .await;
14007    cx.condition(|editor, _| editor.context_menu_visible())
14008        .await;
14009    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14010
14011    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14012        editor
14013            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14014            .unwrap()
14015    });
14016    cx.assert_editor_state(indoc! {"
14017        one.second_completion
14018        two sixth_completionˇ
14019        three sixth_completionˇ
14020        additional edit
14021    "});
14022
14023    apply_additional_edits.await.unwrap();
14024
14025    update_test_language_settings(&mut cx, |settings| {
14026        settings.defaults.show_completions_on_input = Some(false);
14027    });
14028    cx.set_state("editorˇ");
14029    cx.simulate_keystroke(".");
14030    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14031    cx.simulate_keystrokes("c l o");
14032    cx.assert_editor_state("editor.cloˇ");
14033    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14034    cx.update_editor(|editor, window, cx| {
14035        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14036    });
14037    handle_completion_request(
14038        "editor.<clo|>",
14039        vec!["close", "clobber"],
14040        true,
14041        counter.clone(),
14042        &mut cx,
14043    )
14044    .await;
14045    cx.condition(|editor, _| editor.context_menu_visible())
14046        .await;
14047    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14048
14049    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14050        editor
14051            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14052            .unwrap()
14053    });
14054    cx.assert_editor_state("editor.clobberˇ");
14055    handle_resolve_completion_request(&mut cx, None).await;
14056    apply_additional_edits.await.unwrap();
14057}
14058
14059#[gpui::test]
14060async fn test_completion_reuse(cx: &mut TestAppContext) {
14061    init_test(cx, |_| {});
14062
14063    let mut cx = EditorLspTestContext::new_rust(
14064        lsp::ServerCapabilities {
14065            completion_provider: Some(lsp::CompletionOptions {
14066                trigger_characters: Some(vec![".".to_string()]),
14067                ..Default::default()
14068            }),
14069            ..Default::default()
14070        },
14071        cx,
14072    )
14073    .await;
14074
14075    let counter = Arc::new(AtomicUsize::new(0));
14076    cx.set_state("objˇ");
14077    cx.simulate_keystroke(".");
14078
14079    // Initial completion request returns complete results
14080    let is_incomplete = false;
14081    handle_completion_request(
14082        "obj.|<>",
14083        vec!["a", "ab", "abc"],
14084        is_incomplete,
14085        counter.clone(),
14086        &mut cx,
14087    )
14088    .await;
14089    cx.run_until_parked();
14090    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14091    cx.assert_editor_state("obj.ˇ");
14092    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14093
14094    // Type "a" - filters existing completions
14095    cx.simulate_keystroke("a");
14096    cx.run_until_parked();
14097    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14098    cx.assert_editor_state("obj.aˇ");
14099    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14100
14101    // Type "b" - filters existing completions
14102    cx.simulate_keystroke("b");
14103    cx.run_until_parked();
14104    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14105    cx.assert_editor_state("obj.abˇ");
14106    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14107
14108    // Type "c" - filters existing completions
14109    cx.simulate_keystroke("c");
14110    cx.run_until_parked();
14111    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14112    cx.assert_editor_state("obj.abcˇ");
14113    check_displayed_completions(vec!["abc"], &mut cx);
14114
14115    // Backspace to delete "c" - filters existing completions
14116    cx.update_editor(|editor, window, cx| {
14117        editor.backspace(&Backspace, window, cx);
14118    });
14119    cx.run_until_parked();
14120    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14121    cx.assert_editor_state("obj.abˇ");
14122    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14123
14124    // Moving cursor to the left dismisses menu.
14125    cx.update_editor(|editor, window, cx| {
14126        editor.move_left(&MoveLeft, window, cx);
14127    });
14128    cx.run_until_parked();
14129    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14130    cx.assert_editor_state("obj.aˇb");
14131    cx.update_editor(|editor, _, _| {
14132        assert_eq!(editor.context_menu_visible(), false);
14133    });
14134
14135    // Type "b" - new request
14136    cx.simulate_keystroke("b");
14137    let is_incomplete = false;
14138    handle_completion_request(
14139        "obj.<ab|>a",
14140        vec!["ab", "abc"],
14141        is_incomplete,
14142        counter.clone(),
14143        &mut cx,
14144    )
14145    .await;
14146    cx.run_until_parked();
14147    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14148    cx.assert_editor_state("obj.abˇb");
14149    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14150
14151    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14152    cx.update_editor(|editor, window, cx| {
14153        editor.backspace(&Backspace, window, cx);
14154    });
14155    let is_incomplete = false;
14156    handle_completion_request(
14157        "obj.<a|>b",
14158        vec!["a", "ab", "abc"],
14159        is_incomplete,
14160        counter.clone(),
14161        &mut cx,
14162    )
14163    .await;
14164    cx.run_until_parked();
14165    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14166    cx.assert_editor_state("obj.aˇb");
14167    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14168
14169    // Backspace to delete "a" - dismisses menu.
14170    cx.update_editor(|editor, window, cx| {
14171        editor.backspace(&Backspace, window, cx);
14172    });
14173    cx.run_until_parked();
14174    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14175    cx.assert_editor_state("obj.ˇb");
14176    cx.update_editor(|editor, _, _| {
14177        assert_eq!(editor.context_menu_visible(), false);
14178    });
14179}
14180
14181#[gpui::test]
14182async fn test_word_completion(cx: &mut TestAppContext) {
14183    let lsp_fetch_timeout_ms = 10;
14184    init_test(cx, |language_settings| {
14185        language_settings.defaults.completions = Some(CompletionSettingsContent {
14186            words_min_length: Some(0),
14187            lsp_fetch_timeout_ms: Some(10),
14188            lsp_insert_mode: Some(LspInsertMode::Insert),
14189            ..Default::default()
14190        });
14191    });
14192
14193    let mut cx = EditorLspTestContext::new_rust(
14194        lsp::ServerCapabilities {
14195            completion_provider: Some(lsp::CompletionOptions {
14196                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14197                ..lsp::CompletionOptions::default()
14198            }),
14199            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14200            ..lsp::ServerCapabilities::default()
14201        },
14202        cx,
14203    )
14204    .await;
14205
14206    let throttle_completions = Arc::new(AtomicBool::new(false));
14207
14208    let lsp_throttle_completions = throttle_completions.clone();
14209    let _completion_requests_handler =
14210        cx.lsp
14211            .server
14212            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14213                let lsp_throttle_completions = lsp_throttle_completions.clone();
14214                let cx = cx.clone();
14215                async move {
14216                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14217                        cx.background_executor()
14218                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14219                            .await;
14220                    }
14221                    Ok(Some(lsp::CompletionResponse::Array(vec![
14222                        lsp::CompletionItem {
14223                            label: "first".into(),
14224                            ..lsp::CompletionItem::default()
14225                        },
14226                        lsp::CompletionItem {
14227                            label: "last".into(),
14228                            ..lsp::CompletionItem::default()
14229                        },
14230                    ])))
14231                }
14232            });
14233
14234    cx.set_state(indoc! {"
14235        oneˇ
14236        two
14237        three
14238    "});
14239    cx.simulate_keystroke(".");
14240    cx.executor().run_until_parked();
14241    cx.condition(|editor, _| editor.context_menu_visible())
14242        .await;
14243    cx.update_editor(|editor, window, cx| {
14244        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14245        {
14246            assert_eq!(
14247                completion_menu_entries(menu),
14248                &["first", "last"],
14249                "When LSP server is fast to reply, no fallback word completions are used"
14250            );
14251        } else {
14252            panic!("expected completion menu to be open");
14253        }
14254        editor.cancel(&Cancel, window, cx);
14255    });
14256    cx.executor().run_until_parked();
14257    cx.condition(|editor, _| !editor.context_menu_visible())
14258        .await;
14259
14260    throttle_completions.store(true, atomic::Ordering::Release);
14261    cx.simulate_keystroke(".");
14262    cx.executor()
14263        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14264    cx.executor().run_until_parked();
14265    cx.condition(|editor, _| editor.context_menu_visible())
14266        .await;
14267    cx.update_editor(|editor, _, _| {
14268        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14269        {
14270            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14271                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14272        } else {
14273            panic!("expected completion menu to be open");
14274        }
14275    });
14276}
14277
14278#[gpui::test]
14279async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14280    init_test(cx, |language_settings| {
14281        language_settings.defaults.completions = Some(CompletionSettingsContent {
14282            words: Some(WordsCompletionMode::Enabled),
14283            words_min_length: Some(0),
14284            lsp_insert_mode: Some(LspInsertMode::Insert),
14285            ..Default::default()
14286        });
14287    });
14288
14289    let mut cx = EditorLspTestContext::new_rust(
14290        lsp::ServerCapabilities {
14291            completion_provider: Some(lsp::CompletionOptions {
14292                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14293                ..lsp::CompletionOptions::default()
14294            }),
14295            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14296            ..lsp::ServerCapabilities::default()
14297        },
14298        cx,
14299    )
14300    .await;
14301
14302    let _completion_requests_handler =
14303        cx.lsp
14304            .server
14305            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14306                Ok(Some(lsp::CompletionResponse::Array(vec![
14307                    lsp::CompletionItem {
14308                        label: "first".into(),
14309                        ..lsp::CompletionItem::default()
14310                    },
14311                    lsp::CompletionItem {
14312                        label: "last".into(),
14313                        ..lsp::CompletionItem::default()
14314                    },
14315                ])))
14316            });
14317
14318    cx.set_state(indoc! {"ˇ
14319        first
14320        last
14321        second
14322    "});
14323    cx.simulate_keystroke(".");
14324    cx.executor().run_until_parked();
14325    cx.condition(|editor, _| editor.context_menu_visible())
14326        .await;
14327    cx.update_editor(|editor, _, _| {
14328        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14329        {
14330            assert_eq!(
14331                completion_menu_entries(menu),
14332                &["first", "last", "second"],
14333                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14334            );
14335        } else {
14336            panic!("expected completion menu to be open");
14337        }
14338    });
14339}
14340
14341#[gpui::test]
14342async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14343    init_test(cx, |language_settings| {
14344        language_settings.defaults.completions = Some(CompletionSettingsContent {
14345            words: Some(WordsCompletionMode::Disabled),
14346            words_min_length: Some(0),
14347            lsp_insert_mode: Some(LspInsertMode::Insert),
14348            ..Default::default()
14349        });
14350    });
14351
14352    let mut cx = EditorLspTestContext::new_rust(
14353        lsp::ServerCapabilities {
14354            completion_provider: Some(lsp::CompletionOptions {
14355                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14356                ..lsp::CompletionOptions::default()
14357            }),
14358            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14359            ..lsp::ServerCapabilities::default()
14360        },
14361        cx,
14362    )
14363    .await;
14364
14365    let _completion_requests_handler =
14366        cx.lsp
14367            .server
14368            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14369                panic!("LSP completions should not be queried when dealing with word completions")
14370            });
14371
14372    cx.set_state(indoc! {"ˇ
14373        first
14374        last
14375        second
14376    "});
14377    cx.update_editor(|editor, window, cx| {
14378        editor.show_word_completions(&ShowWordCompletions, window, cx);
14379    });
14380    cx.executor().run_until_parked();
14381    cx.condition(|editor, _| editor.context_menu_visible())
14382        .await;
14383    cx.update_editor(|editor, _, _| {
14384        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14385        {
14386            assert_eq!(
14387                completion_menu_entries(menu),
14388                &["first", "last", "second"],
14389                "`ShowWordCompletions` action should show word completions"
14390            );
14391        } else {
14392            panic!("expected completion menu to be open");
14393        }
14394    });
14395
14396    cx.simulate_keystroke("l");
14397    cx.executor().run_until_parked();
14398    cx.condition(|editor, _| editor.context_menu_visible())
14399        .await;
14400    cx.update_editor(|editor, _, _| {
14401        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14402        {
14403            assert_eq!(
14404                completion_menu_entries(menu),
14405                &["last"],
14406                "After showing word completions, further editing should filter them and not query the LSP"
14407            );
14408        } else {
14409            panic!("expected completion menu to be open");
14410        }
14411    });
14412}
14413
14414#[gpui::test]
14415async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14416    init_test(cx, |language_settings| {
14417        language_settings.defaults.completions = Some(CompletionSettingsContent {
14418            words_min_length: Some(0),
14419            lsp: Some(false),
14420            lsp_insert_mode: Some(LspInsertMode::Insert),
14421            ..Default::default()
14422        });
14423    });
14424
14425    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14426
14427    cx.set_state(indoc! {"ˇ
14428        0_usize
14429        let
14430        33
14431        4.5f32
14432    "});
14433    cx.update_editor(|editor, window, cx| {
14434        editor.show_completions(&ShowCompletions::default(), window, cx);
14435    });
14436    cx.executor().run_until_parked();
14437    cx.condition(|editor, _| editor.context_menu_visible())
14438        .await;
14439    cx.update_editor(|editor, window, cx| {
14440        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14441        {
14442            assert_eq!(
14443                completion_menu_entries(menu),
14444                &["let"],
14445                "With no digits in the completion query, no digits should be in the word completions"
14446            );
14447        } else {
14448            panic!("expected completion menu to be open");
14449        }
14450        editor.cancel(&Cancel, window, cx);
14451    });
14452
14453    cx.set_state(indoc! {"14454        0_usize
14455        let
14456        3
14457        33.35f32
14458    "});
14459    cx.update_editor(|editor, window, cx| {
14460        editor.show_completions(&ShowCompletions::default(), window, cx);
14461    });
14462    cx.executor().run_until_parked();
14463    cx.condition(|editor, _| editor.context_menu_visible())
14464        .await;
14465    cx.update_editor(|editor, _, _| {
14466        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14467        {
14468            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14469                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14470        } else {
14471            panic!("expected completion menu to be open");
14472        }
14473    });
14474}
14475
14476#[gpui::test]
14477async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
14478    init_test(cx, |language_settings| {
14479        language_settings.defaults.completions = Some(CompletionSettingsContent {
14480            words: Some(WordsCompletionMode::Enabled),
14481            words_min_length: Some(3),
14482            lsp_insert_mode: Some(LspInsertMode::Insert),
14483            ..Default::default()
14484        });
14485    });
14486
14487    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14488    cx.set_state(indoc! {"ˇ
14489        wow
14490        wowen
14491        wowser
14492    "});
14493    cx.simulate_keystroke("w");
14494    cx.executor().run_until_parked();
14495    cx.update_editor(|editor, _, _| {
14496        if editor.context_menu.borrow_mut().is_some() {
14497            panic!(
14498                "expected completion menu to be hidden, as words completion threshold is not met"
14499            );
14500        }
14501    });
14502
14503    cx.update_editor(|editor, window, cx| {
14504        editor.show_word_completions(&ShowWordCompletions, window, cx);
14505    });
14506    cx.executor().run_until_parked();
14507    cx.update_editor(|editor, window, cx| {
14508        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14509        {
14510            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");
14511        } else {
14512            panic!("expected completion menu to be open after the word completions are called with an action");
14513        }
14514
14515        editor.cancel(&Cancel, window, cx);
14516    });
14517    cx.update_editor(|editor, _, _| {
14518        if editor.context_menu.borrow_mut().is_some() {
14519            panic!("expected completion menu to be hidden after canceling");
14520        }
14521    });
14522
14523    cx.simulate_keystroke("o");
14524    cx.executor().run_until_parked();
14525    cx.update_editor(|editor, _, _| {
14526        if editor.context_menu.borrow_mut().is_some() {
14527            panic!(
14528                "expected completion menu to be hidden, as words completion threshold is not met still"
14529            );
14530        }
14531    });
14532
14533    cx.simulate_keystroke("w");
14534    cx.executor().run_until_parked();
14535    cx.update_editor(|editor, _, _| {
14536        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14537        {
14538            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
14539        } else {
14540            panic!("expected completion menu to be open after the word completions threshold is met");
14541        }
14542    });
14543}
14544
14545#[gpui::test]
14546async fn test_word_completions_disabled(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(0),
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.update_editor(|editor, _, _| {
14558        editor.disable_word_completions();
14559    });
14560    cx.set_state(indoc! {"ˇ
14561        wow
14562        wowen
14563        wowser
14564    "});
14565    cx.simulate_keystroke("w");
14566    cx.executor().run_until_parked();
14567    cx.update_editor(|editor, _, _| {
14568        if editor.context_menu.borrow_mut().is_some() {
14569            panic!(
14570                "expected completion menu to be hidden, as words completion are disabled for this editor"
14571            );
14572        }
14573    });
14574
14575    cx.update_editor(|editor, window, cx| {
14576        editor.show_word_completions(&ShowWordCompletions, window, cx);
14577    });
14578    cx.executor().run_until_parked();
14579    cx.update_editor(|editor, _, _| {
14580        if editor.context_menu.borrow_mut().is_some() {
14581            panic!(
14582                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
14583            );
14584        }
14585    });
14586}
14587
14588fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
14589    let position = || lsp::Position {
14590        line: params.text_document_position.position.line,
14591        character: params.text_document_position.position.character,
14592    };
14593    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14594        range: lsp::Range {
14595            start: position(),
14596            end: position(),
14597        },
14598        new_text: text.to_string(),
14599    }))
14600}
14601
14602#[gpui::test]
14603async fn test_multiline_completion(cx: &mut TestAppContext) {
14604    init_test(cx, |_| {});
14605
14606    let fs = FakeFs::new(cx.executor());
14607    fs.insert_tree(
14608        path!("/a"),
14609        json!({
14610            "main.ts": "a",
14611        }),
14612    )
14613    .await;
14614
14615    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14616    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14617    let typescript_language = Arc::new(Language::new(
14618        LanguageConfig {
14619            name: "TypeScript".into(),
14620            matcher: LanguageMatcher {
14621                path_suffixes: vec!["ts".to_string()],
14622                ..LanguageMatcher::default()
14623            },
14624            line_comments: vec!["// ".into()],
14625            ..LanguageConfig::default()
14626        },
14627        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14628    ));
14629    language_registry.add(typescript_language.clone());
14630    let mut fake_servers = language_registry.register_fake_lsp(
14631        "TypeScript",
14632        FakeLspAdapter {
14633            capabilities: lsp::ServerCapabilities {
14634                completion_provider: Some(lsp::CompletionOptions {
14635                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14636                    ..lsp::CompletionOptions::default()
14637                }),
14638                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14639                ..lsp::ServerCapabilities::default()
14640            },
14641            // Emulate vtsls label generation
14642            label_for_completion: Some(Box::new(|item, _| {
14643                let text = if let Some(description) = item
14644                    .label_details
14645                    .as_ref()
14646                    .and_then(|label_details| label_details.description.as_ref())
14647                {
14648                    format!("{} {}", item.label, description)
14649                } else if let Some(detail) = &item.detail {
14650                    format!("{} {}", item.label, detail)
14651                } else {
14652                    item.label.clone()
14653                };
14654                let len = text.len();
14655                Some(language::CodeLabel {
14656                    text,
14657                    runs: Vec::new(),
14658                    filter_range: 0..len,
14659                })
14660            })),
14661            ..FakeLspAdapter::default()
14662        },
14663    );
14664    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14665    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14666    let worktree_id = workspace
14667        .update(cx, |workspace, _window, cx| {
14668            workspace.project().update(cx, |project, cx| {
14669                project.worktrees(cx).next().unwrap().read(cx).id()
14670            })
14671        })
14672        .unwrap();
14673    let _buffer = project
14674        .update(cx, |project, cx| {
14675            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
14676        })
14677        .await
14678        .unwrap();
14679    let editor = workspace
14680        .update(cx, |workspace, window, cx| {
14681            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
14682        })
14683        .unwrap()
14684        .await
14685        .unwrap()
14686        .downcast::<Editor>()
14687        .unwrap();
14688    let fake_server = fake_servers.next().await.unwrap();
14689
14690    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
14691    let multiline_label_2 = "a\nb\nc\n";
14692    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
14693    let multiline_description = "d\ne\nf\n";
14694    let multiline_detail_2 = "g\nh\ni\n";
14695
14696    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
14697        move |params, _| async move {
14698            Ok(Some(lsp::CompletionResponse::Array(vec![
14699                lsp::CompletionItem {
14700                    label: multiline_label.to_string(),
14701                    text_edit: gen_text_edit(&params, "new_text_1"),
14702                    ..lsp::CompletionItem::default()
14703                },
14704                lsp::CompletionItem {
14705                    label: "single line label 1".to_string(),
14706                    detail: Some(multiline_detail.to_string()),
14707                    text_edit: gen_text_edit(&params, "new_text_2"),
14708                    ..lsp::CompletionItem::default()
14709                },
14710                lsp::CompletionItem {
14711                    label: "single line label 2".to_string(),
14712                    label_details: Some(lsp::CompletionItemLabelDetails {
14713                        description: Some(multiline_description.to_string()),
14714                        detail: None,
14715                    }),
14716                    text_edit: gen_text_edit(&params, "new_text_2"),
14717                    ..lsp::CompletionItem::default()
14718                },
14719                lsp::CompletionItem {
14720                    label: multiline_label_2.to_string(),
14721                    detail: Some(multiline_detail_2.to_string()),
14722                    text_edit: gen_text_edit(&params, "new_text_3"),
14723                    ..lsp::CompletionItem::default()
14724                },
14725                lsp::CompletionItem {
14726                    label: "Label with many     spaces and \t but without newlines".to_string(),
14727                    detail: Some(
14728                        "Details with many     spaces and \t but without newlines".to_string(),
14729                    ),
14730                    text_edit: gen_text_edit(&params, "new_text_4"),
14731                    ..lsp::CompletionItem::default()
14732                },
14733            ])))
14734        },
14735    );
14736
14737    editor.update_in(cx, |editor, window, cx| {
14738        cx.focus_self(window);
14739        editor.move_to_end(&MoveToEnd, window, cx);
14740        editor.handle_input(".", window, cx);
14741    });
14742    cx.run_until_parked();
14743    completion_handle.next().await.unwrap();
14744
14745    editor.update(cx, |editor, _| {
14746        assert!(editor.context_menu_visible());
14747        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14748        {
14749            let completion_labels = menu
14750                .completions
14751                .borrow()
14752                .iter()
14753                .map(|c| c.label.text.clone())
14754                .collect::<Vec<_>>();
14755            assert_eq!(
14756                completion_labels,
14757                &[
14758                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
14759                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
14760                    "single line label 2 d e f ",
14761                    "a b c g h i ",
14762                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
14763                ],
14764                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
14765            );
14766
14767            for completion in menu
14768                .completions
14769                .borrow()
14770                .iter() {
14771                    assert_eq!(
14772                        completion.label.filter_range,
14773                        0..completion.label.text.len(),
14774                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
14775                    );
14776                }
14777        } else {
14778            panic!("expected completion menu to be open");
14779        }
14780    });
14781}
14782
14783#[gpui::test]
14784async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
14785    init_test(cx, |_| {});
14786    let mut cx = EditorLspTestContext::new_rust(
14787        lsp::ServerCapabilities {
14788            completion_provider: Some(lsp::CompletionOptions {
14789                trigger_characters: Some(vec![".".to_string()]),
14790                ..Default::default()
14791            }),
14792            ..Default::default()
14793        },
14794        cx,
14795    )
14796    .await;
14797    cx.lsp
14798        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14799            Ok(Some(lsp::CompletionResponse::Array(vec![
14800                lsp::CompletionItem {
14801                    label: "first".into(),
14802                    ..Default::default()
14803                },
14804                lsp::CompletionItem {
14805                    label: "last".into(),
14806                    ..Default::default()
14807                },
14808            ])))
14809        });
14810    cx.set_state("variableˇ");
14811    cx.simulate_keystroke(".");
14812    cx.executor().run_until_parked();
14813
14814    cx.update_editor(|editor, _, _| {
14815        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14816        {
14817            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
14818        } else {
14819            panic!("expected completion menu to be open");
14820        }
14821    });
14822
14823    cx.update_editor(|editor, window, cx| {
14824        editor.move_page_down(&MovePageDown::default(), window, cx);
14825        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14826        {
14827            assert!(
14828                menu.selected_item == 1,
14829                "expected PageDown to select the last item from the context menu"
14830            );
14831        } else {
14832            panic!("expected completion menu to stay open after PageDown");
14833        }
14834    });
14835
14836    cx.update_editor(|editor, window, cx| {
14837        editor.move_page_up(&MovePageUp::default(), window, cx);
14838        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14839        {
14840            assert!(
14841                menu.selected_item == 0,
14842                "expected PageUp to select the first item from the context menu"
14843            );
14844        } else {
14845            panic!("expected completion menu to stay open after PageUp");
14846        }
14847    });
14848}
14849
14850#[gpui::test]
14851async fn test_as_is_completions(cx: &mut TestAppContext) {
14852    init_test(cx, |_| {});
14853    let mut cx = EditorLspTestContext::new_rust(
14854        lsp::ServerCapabilities {
14855            completion_provider: Some(lsp::CompletionOptions {
14856                ..Default::default()
14857            }),
14858            ..Default::default()
14859        },
14860        cx,
14861    )
14862    .await;
14863    cx.lsp
14864        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14865            Ok(Some(lsp::CompletionResponse::Array(vec![
14866                lsp::CompletionItem {
14867                    label: "unsafe".into(),
14868                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14869                        range: lsp::Range {
14870                            start: lsp::Position {
14871                                line: 1,
14872                                character: 2,
14873                            },
14874                            end: lsp::Position {
14875                                line: 1,
14876                                character: 3,
14877                            },
14878                        },
14879                        new_text: "unsafe".to_string(),
14880                    })),
14881                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
14882                    ..Default::default()
14883                },
14884            ])))
14885        });
14886    cx.set_state("fn a() {}\n");
14887    cx.executor().run_until_parked();
14888    cx.update_editor(|editor, window, cx| {
14889        editor.show_completions(
14890            &ShowCompletions {
14891                trigger: Some("\n".into()),
14892            },
14893            window,
14894            cx,
14895        );
14896    });
14897    cx.executor().run_until_parked();
14898
14899    cx.update_editor(|editor, window, cx| {
14900        editor.confirm_completion(&Default::default(), window, cx)
14901    });
14902    cx.executor().run_until_parked();
14903    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
14904}
14905
14906#[gpui::test]
14907async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
14908    init_test(cx, |_| {});
14909    let language =
14910        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
14911    let mut cx = EditorLspTestContext::new(
14912        language,
14913        lsp::ServerCapabilities {
14914            completion_provider: Some(lsp::CompletionOptions {
14915                ..lsp::CompletionOptions::default()
14916            }),
14917            ..lsp::ServerCapabilities::default()
14918        },
14919        cx,
14920    )
14921    .await;
14922
14923    cx.set_state(
14924        "#ifndef BAR_H
14925#define BAR_H
14926
14927#include <stdbool.h>
14928
14929int fn_branch(bool do_branch1, bool do_branch2);
14930
14931#endif // BAR_H
14932ˇ",
14933    );
14934    cx.executor().run_until_parked();
14935    cx.update_editor(|editor, window, cx| {
14936        editor.handle_input("#", window, cx);
14937    });
14938    cx.executor().run_until_parked();
14939    cx.update_editor(|editor, window, cx| {
14940        editor.handle_input("i", window, cx);
14941    });
14942    cx.executor().run_until_parked();
14943    cx.update_editor(|editor, window, cx| {
14944        editor.handle_input("n", window, cx);
14945    });
14946    cx.executor().run_until_parked();
14947    cx.assert_editor_state(
14948        "#ifndef BAR_H
14949#define BAR_H
14950
14951#include <stdbool.h>
14952
14953int fn_branch(bool do_branch1, bool do_branch2);
14954
14955#endif // BAR_H
14956#inˇ",
14957    );
14958
14959    cx.lsp
14960        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14961            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
14962                is_incomplete: false,
14963                item_defaults: None,
14964                items: vec![lsp::CompletionItem {
14965                    kind: Some(lsp::CompletionItemKind::SNIPPET),
14966                    label_details: Some(lsp::CompletionItemLabelDetails {
14967                        detail: Some("header".to_string()),
14968                        description: None,
14969                    }),
14970                    label: " include".to_string(),
14971                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14972                        range: lsp::Range {
14973                            start: lsp::Position {
14974                                line: 8,
14975                                character: 1,
14976                            },
14977                            end: lsp::Position {
14978                                line: 8,
14979                                character: 1,
14980                            },
14981                        },
14982                        new_text: "include \"$0\"".to_string(),
14983                    })),
14984                    sort_text: Some("40b67681include".to_string()),
14985                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
14986                    filter_text: Some("include".to_string()),
14987                    insert_text: Some("include \"$0\"".to_string()),
14988                    ..lsp::CompletionItem::default()
14989                }],
14990            })))
14991        });
14992    cx.update_editor(|editor, window, cx| {
14993        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14994    });
14995    cx.executor().run_until_parked();
14996    cx.update_editor(|editor, window, cx| {
14997        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
14998    });
14999    cx.executor().run_until_parked();
15000    cx.assert_editor_state(
15001        "#ifndef BAR_H
15002#define BAR_H
15003
15004#include <stdbool.h>
15005
15006int fn_branch(bool do_branch1, bool do_branch2);
15007
15008#endif // BAR_H
15009#include \"ˇ\"",
15010    );
15011
15012    cx.lsp
15013        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15014            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15015                is_incomplete: true,
15016                item_defaults: None,
15017                items: vec![lsp::CompletionItem {
15018                    kind: Some(lsp::CompletionItemKind::FILE),
15019                    label: "AGL/".to_string(),
15020                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15021                        range: lsp::Range {
15022                            start: lsp::Position {
15023                                line: 8,
15024                                character: 10,
15025                            },
15026                            end: lsp::Position {
15027                                line: 8,
15028                                character: 11,
15029                            },
15030                        },
15031                        new_text: "AGL/".to_string(),
15032                    })),
15033                    sort_text: Some("40b67681AGL/".to_string()),
15034                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15035                    filter_text: Some("AGL/".to_string()),
15036                    insert_text: Some("AGL/".to_string()),
15037                    ..lsp::CompletionItem::default()
15038                }],
15039            })))
15040        });
15041    cx.update_editor(|editor, window, cx| {
15042        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15043    });
15044    cx.executor().run_until_parked();
15045    cx.update_editor(|editor, window, cx| {
15046        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15047    });
15048    cx.executor().run_until_parked();
15049    cx.assert_editor_state(
15050        r##"#ifndef BAR_H
15051#define BAR_H
15052
15053#include <stdbool.h>
15054
15055int fn_branch(bool do_branch1, bool do_branch2);
15056
15057#endif // BAR_H
15058#include "AGL/ˇ"##,
15059    );
15060
15061    cx.update_editor(|editor, window, cx| {
15062        editor.handle_input("\"", window, cx);
15063    });
15064    cx.executor().run_until_parked();
15065    cx.assert_editor_state(
15066        r##"#ifndef BAR_H
15067#define BAR_H
15068
15069#include <stdbool.h>
15070
15071int fn_branch(bool do_branch1, bool do_branch2);
15072
15073#endif // BAR_H
15074#include "AGL/"ˇ"##,
15075    );
15076}
15077
15078#[gpui::test]
15079async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15080    init_test(cx, |_| {});
15081
15082    let mut cx = EditorLspTestContext::new_rust(
15083        lsp::ServerCapabilities {
15084            completion_provider: Some(lsp::CompletionOptions {
15085                trigger_characters: Some(vec![".".to_string()]),
15086                resolve_provider: Some(true),
15087                ..Default::default()
15088            }),
15089            ..Default::default()
15090        },
15091        cx,
15092    )
15093    .await;
15094
15095    cx.set_state("fn main() { let a = 2ˇ; }");
15096    cx.simulate_keystroke(".");
15097    let completion_item = lsp::CompletionItem {
15098        label: "Some".into(),
15099        kind: Some(lsp::CompletionItemKind::SNIPPET),
15100        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15101        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15102            kind: lsp::MarkupKind::Markdown,
15103            value: "```rust\nSome(2)\n```".to_string(),
15104        })),
15105        deprecated: Some(false),
15106        sort_text: Some("Some".to_string()),
15107        filter_text: Some("Some".to_string()),
15108        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15109        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15110            range: lsp::Range {
15111                start: lsp::Position {
15112                    line: 0,
15113                    character: 22,
15114                },
15115                end: lsp::Position {
15116                    line: 0,
15117                    character: 22,
15118                },
15119            },
15120            new_text: "Some(2)".to_string(),
15121        })),
15122        additional_text_edits: Some(vec![lsp::TextEdit {
15123            range: lsp::Range {
15124                start: lsp::Position {
15125                    line: 0,
15126                    character: 20,
15127                },
15128                end: lsp::Position {
15129                    line: 0,
15130                    character: 22,
15131                },
15132            },
15133            new_text: "".to_string(),
15134        }]),
15135        ..Default::default()
15136    };
15137
15138    let closure_completion_item = completion_item.clone();
15139    let counter = Arc::new(AtomicUsize::new(0));
15140    let counter_clone = counter.clone();
15141    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15142        let task_completion_item = closure_completion_item.clone();
15143        counter_clone.fetch_add(1, atomic::Ordering::Release);
15144        async move {
15145            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15146                is_incomplete: true,
15147                item_defaults: None,
15148                items: vec![task_completion_item],
15149            })))
15150        }
15151    });
15152
15153    cx.condition(|editor, _| editor.context_menu_visible())
15154        .await;
15155    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15156    assert!(request.next().await.is_some());
15157    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15158
15159    cx.simulate_keystrokes("S o m");
15160    cx.condition(|editor, _| editor.context_menu_visible())
15161        .await;
15162    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15163    assert!(request.next().await.is_some());
15164    assert!(request.next().await.is_some());
15165    assert!(request.next().await.is_some());
15166    request.close();
15167    assert!(request.next().await.is_none());
15168    assert_eq!(
15169        counter.load(atomic::Ordering::Acquire),
15170        4,
15171        "With the completions menu open, only one LSP request should happen per input"
15172    );
15173}
15174
15175#[gpui::test]
15176async fn test_toggle_comment(cx: &mut TestAppContext) {
15177    init_test(cx, |_| {});
15178    let mut cx = EditorTestContext::new(cx).await;
15179    let language = Arc::new(Language::new(
15180        LanguageConfig {
15181            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15182            ..Default::default()
15183        },
15184        Some(tree_sitter_rust::LANGUAGE.into()),
15185    ));
15186    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15187
15188    // If multiple selections intersect a line, the line is only toggled once.
15189    cx.set_state(indoc! {"
15190        fn a() {
15191            «//b();
15192            ˇ»// «c();
15193            //ˇ»  d();
15194        }
15195    "});
15196
15197    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15198
15199    cx.assert_editor_state(indoc! {"
15200        fn a() {
15201            «b();
15202            c();
15203            ˇ» d();
15204        }
15205    "});
15206
15207    // The comment prefix is inserted at the same column for every line in a
15208    // selection.
15209    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15210
15211    cx.assert_editor_state(indoc! {"
15212        fn a() {
15213            // «b();
15214            // c();
15215            ˇ»//  d();
15216        }
15217    "});
15218
15219    // If a selection ends at the beginning of a line, that line is not toggled.
15220    cx.set_selections_state(indoc! {"
15221        fn a() {
15222            // b();
15223            «// c();
15224        ˇ»    //  d();
15225        }
15226    "});
15227
15228    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15229
15230    cx.assert_editor_state(indoc! {"
15231        fn a() {
15232            // b();
15233            «c();
15234        ˇ»    //  d();
15235        }
15236    "});
15237
15238    // If a selection span a single line and is empty, the line is toggled.
15239    cx.set_state(indoc! {"
15240        fn a() {
15241            a();
15242            b();
15243        ˇ
15244        }
15245    "});
15246
15247    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15248
15249    cx.assert_editor_state(indoc! {"
15250        fn a() {
15251            a();
15252            b();
15253        //•ˇ
15254        }
15255    "});
15256
15257    // If a selection span multiple lines, empty lines are not toggled.
15258    cx.set_state(indoc! {"
15259        fn a() {
15260            «a();
15261
15262            c();ˇ»
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            // «a();
15271
15272            // c();ˇ»
15273        }
15274    "});
15275
15276    // If a selection includes multiple comment prefixes, all lines are uncommented.
15277    cx.set_state(indoc! {"
15278        fn a() {
15279            «// a();
15280            /// b();
15281            //! c();ˇ»
15282        }
15283    "});
15284
15285    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15286
15287    cx.assert_editor_state(indoc! {"
15288        fn a() {
15289            «a();
15290            b();
15291            c();ˇ»
15292        }
15293    "});
15294}
15295
15296#[gpui::test]
15297async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15298    init_test(cx, |_| {});
15299    let mut cx = EditorTestContext::new(cx).await;
15300    let language = Arc::new(Language::new(
15301        LanguageConfig {
15302            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15303            ..Default::default()
15304        },
15305        Some(tree_sitter_rust::LANGUAGE.into()),
15306    ));
15307    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15308
15309    let toggle_comments = &ToggleComments {
15310        advance_downwards: false,
15311        ignore_indent: true,
15312    };
15313
15314    // If multiple selections intersect a line, the line is only toggled once.
15315    cx.set_state(indoc! {"
15316        fn a() {
15317        //    «b();
15318        //    c();
15319        //    ˇ» d();
15320        }
15321    "});
15322
15323    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15324
15325    cx.assert_editor_state(indoc! {"
15326        fn a() {
15327            «b();
15328            c();
15329            ˇ» d();
15330        }
15331    "});
15332
15333    // The comment prefix is inserted at the beginning of each line
15334    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15335
15336    cx.assert_editor_state(indoc! {"
15337        fn a() {
15338        //    «b();
15339        //    c();
15340        //    ˇ» d();
15341        }
15342    "});
15343
15344    // If a selection ends at the beginning of a line, that line is not toggled.
15345    cx.set_selections_state(indoc! {"
15346        fn a() {
15347        //    b();
15348        //    «c();
15349        ˇ»//     d();
15350        }
15351    "});
15352
15353    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15354
15355    cx.assert_editor_state(indoc! {"
15356        fn a() {
15357        //    b();
15358            «c();
15359        ˇ»//     d();
15360        }
15361    "});
15362
15363    // If a selection span a single line and is empty, the line is toggled.
15364    cx.set_state(indoc! {"
15365        fn a() {
15366            a();
15367            b();
15368        ˇ
15369        }
15370    "});
15371
15372    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15373
15374    cx.assert_editor_state(indoc! {"
15375        fn a() {
15376            a();
15377            b();
15378        //ˇ
15379        }
15380    "});
15381
15382    // If a selection span multiple lines, empty lines are not toggled.
15383    cx.set_state(indoc! {"
15384        fn a() {
15385            «a();
15386
15387            c();ˇ»
15388        }
15389    "});
15390
15391    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15392
15393    cx.assert_editor_state(indoc! {"
15394        fn a() {
15395        //    «a();
15396
15397        //    c();ˇ»
15398        }
15399    "});
15400
15401    // If a selection includes multiple comment prefixes, all lines are uncommented.
15402    cx.set_state(indoc! {"
15403        fn a() {
15404        //    «a();
15405        ///    b();
15406        //!    c();ˇ»
15407        }
15408    "});
15409
15410    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15411
15412    cx.assert_editor_state(indoc! {"
15413        fn a() {
15414            «a();
15415            b();
15416            c();ˇ»
15417        }
15418    "});
15419}
15420
15421#[gpui::test]
15422async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15423    init_test(cx, |_| {});
15424
15425    let language = Arc::new(Language::new(
15426        LanguageConfig {
15427            line_comments: vec!["// ".into()],
15428            ..Default::default()
15429        },
15430        Some(tree_sitter_rust::LANGUAGE.into()),
15431    ));
15432
15433    let mut cx = EditorTestContext::new(cx).await;
15434
15435    cx.language_registry().add(language.clone());
15436    cx.update_buffer(|buffer, cx| {
15437        buffer.set_language(Some(language), cx);
15438    });
15439
15440    let toggle_comments = &ToggleComments {
15441        advance_downwards: true,
15442        ignore_indent: false,
15443    };
15444
15445    // Single cursor on one line -> advance
15446    // Cursor moves horizontally 3 characters as well on non-blank line
15447    cx.set_state(indoc!(
15448        "fn a() {
15449             ˇdog();
15450             cat();
15451        }"
15452    ));
15453    cx.update_editor(|editor, window, cx| {
15454        editor.toggle_comments(toggle_comments, window, cx);
15455    });
15456    cx.assert_editor_state(indoc!(
15457        "fn a() {
15458             // dog();
15459             catˇ();
15460        }"
15461    ));
15462
15463    // Single selection on one line -> don't advance
15464    cx.set_state(indoc!(
15465        "fn a() {
15466             «dog()ˇ»;
15467             cat();
15468        }"
15469    ));
15470    cx.update_editor(|editor, window, cx| {
15471        editor.toggle_comments(toggle_comments, window, cx);
15472    });
15473    cx.assert_editor_state(indoc!(
15474        "fn a() {
15475             // «dog()ˇ»;
15476             cat();
15477        }"
15478    ));
15479
15480    // Multiple cursors on one line -> advance
15481    cx.set_state(indoc!(
15482        "fn a() {
15483             ˇdˇog();
15484             cat();
15485        }"
15486    ));
15487    cx.update_editor(|editor, window, cx| {
15488        editor.toggle_comments(toggle_comments, window, cx);
15489    });
15490    cx.assert_editor_state(indoc!(
15491        "fn a() {
15492             // dog();
15493             catˇ(ˇ);
15494        }"
15495    ));
15496
15497    // Multiple cursors on one line, with selection -> don't advance
15498    cx.set_state(indoc!(
15499        "fn a() {
15500             ˇdˇog«()ˇ»;
15501             cat();
15502        }"
15503    ));
15504    cx.update_editor(|editor, window, cx| {
15505        editor.toggle_comments(toggle_comments, window, cx);
15506    });
15507    cx.assert_editor_state(indoc!(
15508        "fn a() {
15509             // ˇdˇog«()ˇ»;
15510             cat();
15511        }"
15512    ));
15513
15514    // Single cursor on one line -> advance
15515    // Cursor moves to column 0 on blank line
15516    cx.set_state(indoc!(
15517        "fn a() {
15518             ˇdog();
15519
15520             cat();
15521        }"
15522    ));
15523    cx.update_editor(|editor, window, cx| {
15524        editor.toggle_comments(toggle_comments, window, cx);
15525    });
15526    cx.assert_editor_state(indoc!(
15527        "fn a() {
15528             // dog();
15529        ˇ
15530             cat();
15531        }"
15532    ));
15533
15534    // Single cursor on one line -> advance
15535    // Cursor starts and ends at column 0
15536    cx.set_state(indoc!(
15537        "fn a() {
15538         ˇ    dog();
15539             cat();
15540        }"
15541    ));
15542    cx.update_editor(|editor, window, cx| {
15543        editor.toggle_comments(toggle_comments, window, cx);
15544    });
15545    cx.assert_editor_state(indoc!(
15546        "fn a() {
15547             // dog();
15548         ˇ    cat();
15549        }"
15550    ));
15551}
15552
15553#[gpui::test]
15554async fn test_toggle_block_comment(cx: &mut TestAppContext) {
15555    init_test(cx, |_| {});
15556
15557    let mut cx = EditorTestContext::new(cx).await;
15558
15559    let html_language = Arc::new(
15560        Language::new(
15561            LanguageConfig {
15562                name: "HTML".into(),
15563                block_comment: Some(BlockCommentConfig {
15564                    start: "<!-- ".into(),
15565                    prefix: "".into(),
15566                    end: " -->".into(),
15567                    tab_size: 0,
15568                }),
15569                ..Default::default()
15570            },
15571            Some(tree_sitter_html::LANGUAGE.into()),
15572        )
15573        .with_injection_query(
15574            r#"
15575            (script_element
15576                (raw_text) @injection.content
15577                (#set! injection.language "javascript"))
15578            "#,
15579        )
15580        .unwrap(),
15581    );
15582
15583    let javascript_language = Arc::new(Language::new(
15584        LanguageConfig {
15585            name: "JavaScript".into(),
15586            line_comments: vec!["// ".into()],
15587            ..Default::default()
15588        },
15589        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15590    ));
15591
15592    cx.language_registry().add(html_language.clone());
15593    cx.language_registry().add(javascript_language);
15594    cx.update_buffer(|buffer, cx| {
15595        buffer.set_language(Some(html_language), cx);
15596    });
15597
15598    // Toggle comments for empty selections
15599    cx.set_state(
15600        &r#"
15601            <p>A</p>ˇ
15602            <p>B</p>ˇ
15603            <p>C</p>ˇ
15604        "#
15605        .unindent(),
15606    );
15607    cx.update_editor(|editor, window, cx| {
15608        editor.toggle_comments(&ToggleComments::default(), window, cx)
15609    });
15610    cx.assert_editor_state(
15611        &r#"
15612            <!-- <p>A</p>ˇ -->
15613            <!-- <p>B</p>ˇ -->
15614            <!-- <p>C</p>ˇ -->
15615        "#
15616        .unindent(),
15617    );
15618    cx.update_editor(|editor, window, cx| {
15619        editor.toggle_comments(&ToggleComments::default(), window, cx)
15620    });
15621    cx.assert_editor_state(
15622        &r#"
15623            <p>A</p>ˇ
15624            <p>B</p>ˇ
15625            <p>C</p>ˇ
15626        "#
15627        .unindent(),
15628    );
15629
15630    // Toggle comments for mixture of empty and non-empty selections, where
15631    // multiple selections occupy a given line.
15632    cx.set_state(
15633        &r#"
15634            <p>A«</p>
15635            <p>ˇ»B</p>ˇ
15636            <p>C«</p>
15637            <p>ˇ»D</p>ˇ
15638        "#
15639        .unindent(),
15640    );
15641
15642    cx.update_editor(|editor, window, cx| {
15643        editor.toggle_comments(&ToggleComments::default(), window, cx)
15644    });
15645    cx.assert_editor_state(
15646        &r#"
15647            <!-- <p>A«</p>
15648            <p>ˇ»B</p>ˇ -->
15649            <!-- <p>C«</p>
15650            <p>ˇ»D</p>ˇ -->
15651        "#
15652        .unindent(),
15653    );
15654    cx.update_editor(|editor, window, cx| {
15655        editor.toggle_comments(&ToggleComments::default(), window, cx)
15656    });
15657    cx.assert_editor_state(
15658        &r#"
15659            <p>A«</p>
15660            <p>ˇ»B</p>ˇ
15661            <p>C«</p>
15662            <p>ˇ»D</p>ˇ
15663        "#
15664        .unindent(),
15665    );
15666
15667    // Toggle comments when different languages are active for different
15668    // selections.
15669    cx.set_state(
15670        &r#"
15671            ˇ<script>
15672                ˇvar x = new Y();
15673            ˇ</script>
15674        "#
15675        .unindent(),
15676    );
15677    cx.executor().run_until_parked();
15678    cx.update_editor(|editor, window, cx| {
15679        editor.toggle_comments(&ToggleComments::default(), window, cx)
15680    });
15681    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
15682    // Uncommenting and commenting from this position brings in even more wrong artifacts.
15683    cx.assert_editor_state(
15684        &r#"
15685            <!-- ˇ<script> -->
15686                // ˇvar x = new Y();
15687            <!-- ˇ</script> -->
15688        "#
15689        .unindent(),
15690    );
15691}
15692
15693#[gpui::test]
15694fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
15695    init_test(cx, |_| {});
15696
15697    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15698    let multibuffer = cx.new(|cx| {
15699        let mut multibuffer = MultiBuffer::new(ReadWrite);
15700        multibuffer.push_excerpts(
15701            buffer.clone(),
15702            [
15703                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
15704                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
15705            ],
15706            cx,
15707        );
15708        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
15709        multibuffer
15710    });
15711
15712    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15713    editor.update_in(cx, |editor, window, cx| {
15714        assert_eq!(editor.text(cx), "aaaa\nbbbb");
15715        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15716            s.select_ranges([
15717                Point::new(0, 0)..Point::new(0, 0),
15718                Point::new(1, 0)..Point::new(1, 0),
15719            ])
15720        });
15721
15722        editor.handle_input("X", window, cx);
15723        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
15724        assert_eq!(
15725            editor.selections.ranges(cx),
15726            [
15727                Point::new(0, 1)..Point::new(0, 1),
15728                Point::new(1, 1)..Point::new(1, 1),
15729            ]
15730        );
15731
15732        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
15733        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15734            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
15735        });
15736        editor.backspace(&Default::default(), window, cx);
15737        assert_eq!(editor.text(cx), "Xa\nbbb");
15738        assert_eq!(
15739            editor.selections.ranges(cx),
15740            [Point::new(1, 0)..Point::new(1, 0)]
15741        );
15742
15743        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15744            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
15745        });
15746        editor.backspace(&Default::default(), window, cx);
15747        assert_eq!(editor.text(cx), "X\nbb");
15748        assert_eq!(
15749            editor.selections.ranges(cx),
15750            [Point::new(0, 1)..Point::new(0, 1)]
15751        );
15752    });
15753}
15754
15755#[gpui::test]
15756fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
15757    init_test(cx, |_| {});
15758
15759    let markers = vec![('[', ']').into(), ('(', ')').into()];
15760    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
15761        indoc! {"
15762            [aaaa
15763            (bbbb]
15764            cccc)",
15765        },
15766        markers.clone(),
15767    );
15768    let excerpt_ranges = markers.into_iter().map(|marker| {
15769        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
15770        ExcerptRange::new(context)
15771    });
15772    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
15773    let multibuffer = cx.new(|cx| {
15774        let mut multibuffer = MultiBuffer::new(ReadWrite);
15775        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
15776        multibuffer
15777    });
15778
15779    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15780    editor.update_in(cx, |editor, window, cx| {
15781        let (expected_text, selection_ranges) = marked_text_ranges(
15782            indoc! {"
15783                aaaa
15784                bˇbbb
15785                bˇbbˇb
15786                cccc"
15787            },
15788            true,
15789        );
15790        assert_eq!(editor.text(cx), expected_text);
15791        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15792            s.select_ranges(selection_ranges)
15793        });
15794
15795        editor.handle_input("X", window, cx);
15796
15797        let (expected_text, expected_selections) = marked_text_ranges(
15798            indoc! {"
15799                aaaa
15800                bXˇbbXb
15801                bXˇbbXˇb
15802                cccc"
15803            },
15804            false,
15805        );
15806        assert_eq!(editor.text(cx), expected_text);
15807        assert_eq!(editor.selections.ranges(cx), expected_selections);
15808
15809        editor.newline(&Newline, window, cx);
15810        let (expected_text, expected_selections) = marked_text_ranges(
15811            indoc! {"
15812                aaaa
15813                bX
15814                ˇbbX
15815                b
15816                bX
15817                ˇbbX
15818                ˇb
15819                cccc"
15820            },
15821            false,
15822        );
15823        assert_eq!(editor.text(cx), expected_text);
15824        assert_eq!(editor.selections.ranges(cx), expected_selections);
15825    });
15826}
15827
15828#[gpui::test]
15829fn test_refresh_selections(cx: &mut TestAppContext) {
15830    init_test(cx, |_| {});
15831
15832    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15833    let mut excerpt1_id = None;
15834    let multibuffer = cx.new(|cx| {
15835        let mut multibuffer = MultiBuffer::new(ReadWrite);
15836        excerpt1_id = multibuffer
15837            .push_excerpts(
15838                buffer.clone(),
15839                [
15840                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15841                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15842                ],
15843                cx,
15844            )
15845            .into_iter()
15846            .next();
15847        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15848        multibuffer
15849    });
15850
15851    let editor = cx.add_window(|window, cx| {
15852        let mut editor = build_editor(multibuffer.clone(), window, cx);
15853        let snapshot = editor.snapshot(window, cx);
15854        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15855            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
15856        });
15857        editor.begin_selection(
15858            Point::new(2, 1).to_display_point(&snapshot),
15859            true,
15860            1,
15861            window,
15862            cx,
15863        );
15864        assert_eq!(
15865            editor.selections.ranges(cx),
15866            [
15867                Point::new(1, 3)..Point::new(1, 3),
15868                Point::new(2, 1)..Point::new(2, 1),
15869            ]
15870        );
15871        editor
15872    });
15873
15874    // Refreshing selections is a no-op when excerpts haven't changed.
15875    _ = editor.update(cx, |editor, window, cx| {
15876        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15877        assert_eq!(
15878            editor.selections.ranges(cx),
15879            [
15880                Point::new(1, 3)..Point::new(1, 3),
15881                Point::new(2, 1)..Point::new(2, 1),
15882            ]
15883        );
15884    });
15885
15886    multibuffer.update(cx, |multibuffer, cx| {
15887        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
15888    });
15889    _ = editor.update(cx, |editor, window, cx| {
15890        // Removing an excerpt causes the first selection to become degenerate.
15891        assert_eq!(
15892            editor.selections.ranges(cx),
15893            [
15894                Point::new(0, 0)..Point::new(0, 0),
15895                Point::new(0, 1)..Point::new(0, 1)
15896            ]
15897        );
15898
15899        // Refreshing selections will relocate the first selection to the original buffer
15900        // location.
15901        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15902        assert_eq!(
15903            editor.selections.ranges(cx),
15904            [
15905                Point::new(0, 1)..Point::new(0, 1),
15906                Point::new(0, 3)..Point::new(0, 3)
15907            ]
15908        );
15909        assert!(editor.selections.pending_anchor().is_some());
15910    });
15911}
15912
15913#[gpui::test]
15914fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
15915    init_test(cx, |_| {});
15916
15917    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15918    let mut excerpt1_id = None;
15919    let multibuffer = cx.new(|cx| {
15920        let mut multibuffer = MultiBuffer::new(ReadWrite);
15921        excerpt1_id = multibuffer
15922            .push_excerpts(
15923                buffer.clone(),
15924                [
15925                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15926                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15927                ],
15928                cx,
15929            )
15930            .into_iter()
15931            .next();
15932        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15933        multibuffer
15934    });
15935
15936    let editor = cx.add_window(|window, cx| {
15937        let mut editor = build_editor(multibuffer.clone(), window, cx);
15938        let snapshot = editor.snapshot(window, cx);
15939        editor.begin_selection(
15940            Point::new(1, 3).to_display_point(&snapshot),
15941            false,
15942            1,
15943            window,
15944            cx,
15945        );
15946        assert_eq!(
15947            editor.selections.ranges(cx),
15948            [Point::new(1, 3)..Point::new(1, 3)]
15949        );
15950        editor
15951    });
15952
15953    multibuffer.update(cx, |multibuffer, cx| {
15954        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
15955    });
15956    _ = editor.update(cx, |editor, window, cx| {
15957        assert_eq!(
15958            editor.selections.ranges(cx),
15959            [Point::new(0, 0)..Point::new(0, 0)]
15960        );
15961
15962        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
15963        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15964        assert_eq!(
15965            editor.selections.ranges(cx),
15966            [Point::new(0, 3)..Point::new(0, 3)]
15967        );
15968        assert!(editor.selections.pending_anchor().is_some());
15969    });
15970}
15971
15972#[gpui::test]
15973async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
15974    init_test(cx, |_| {});
15975
15976    let language = Arc::new(
15977        Language::new(
15978            LanguageConfig {
15979                brackets: BracketPairConfig {
15980                    pairs: vec![
15981                        BracketPair {
15982                            start: "{".to_string(),
15983                            end: "}".to_string(),
15984                            close: true,
15985                            surround: true,
15986                            newline: true,
15987                        },
15988                        BracketPair {
15989                            start: "/* ".to_string(),
15990                            end: " */".to_string(),
15991                            close: true,
15992                            surround: true,
15993                            newline: true,
15994                        },
15995                    ],
15996                    ..Default::default()
15997                },
15998                ..Default::default()
15999            },
16000            Some(tree_sitter_rust::LANGUAGE.into()),
16001        )
16002        .with_indents_query("")
16003        .unwrap(),
16004    );
16005
16006    let text = concat!(
16007        "{   }\n",     //
16008        "  x\n",       //
16009        "  /*   */\n", //
16010        "x\n",         //
16011        "{{} }\n",     //
16012    );
16013
16014    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16015    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16016    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16017    editor
16018        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16019        .await;
16020
16021    editor.update_in(cx, |editor, window, cx| {
16022        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16023            s.select_display_ranges([
16024                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16025                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16026                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16027            ])
16028        });
16029        editor.newline(&Newline, window, cx);
16030
16031        assert_eq!(
16032            editor.buffer().read(cx).read(cx).text(),
16033            concat!(
16034                "{ \n",    // Suppress rustfmt
16035                "\n",      //
16036                "}\n",     //
16037                "  x\n",   //
16038                "  /* \n", //
16039                "  \n",    //
16040                "  */\n",  //
16041                "x\n",     //
16042                "{{} \n",  //
16043                "}\n",     //
16044            )
16045        );
16046    });
16047}
16048
16049#[gpui::test]
16050fn test_highlighted_ranges(cx: &mut TestAppContext) {
16051    init_test(cx, |_| {});
16052
16053    let editor = cx.add_window(|window, cx| {
16054        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16055        build_editor(buffer, window, cx)
16056    });
16057
16058    _ = editor.update(cx, |editor, window, cx| {
16059        struct Type1;
16060        struct Type2;
16061
16062        let buffer = editor.buffer.read(cx).snapshot(cx);
16063
16064        let anchor_range =
16065            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16066
16067        editor.highlight_background::<Type1>(
16068            &[
16069                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16070                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16071                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16072                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16073            ],
16074            |_| Hsla::red(),
16075            cx,
16076        );
16077        editor.highlight_background::<Type2>(
16078            &[
16079                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16080                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16081                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16082                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16083            ],
16084            |_| Hsla::green(),
16085            cx,
16086        );
16087
16088        let snapshot = editor.snapshot(window, cx);
16089        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16090            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16091            &snapshot,
16092            cx.theme(),
16093        );
16094        assert_eq!(
16095            highlighted_ranges,
16096            &[
16097                (
16098                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16099                    Hsla::green(),
16100                ),
16101                (
16102                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16103                    Hsla::red(),
16104                ),
16105                (
16106                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16107                    Hsla::green(),
16108                ),
16109                (
16110                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16111                    Hsla::red(),
16112                ),
16113            ]
16114        );
16115        assert_eq!(
16116            editor.sorted_background_highlights_in_range(
16117                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16118                &snapshot,
16119                cx.theme(),
16120            ),
16121            &[(
16122                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16123                Hsla::red(),
16124            )]
16125        );
16126    });
16127}
16128
16129#[gpui::test]
16130async fn test_following(cx: &mut TestAppContext) {
16131    init_test(cx, |_| {});
16132
16133    let fs = FakeFs::new(cx.executor());
16134    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16135
16136    let buffer = project.update(cx, |project, cx| {
16137        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16138        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16139    });
16140    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16141    let follower = cx.update(|cx| {
16142        cx.open_window(
16143            WindowOptions {
16144                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16145                    gpui::Point::new(px(0.), px(0.)),
16146                    gpui::Point::new(px(10.), px(80.)),
16147                ))),
16148                ..Default::default()
16149            },
16150            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16151        )
16152        .unwrap()
16153    });
16154
16155    let is_still_following = Rc::new(RefCell::new(true));
16156    let follower_edit_event_count = Rc::new(RefCell::new(0));
16157    let pending_update = Rc::new(RefCell::new(None));
16158    let leader_entity = leader.root(cx).unwrap();
16159    let follower_entity = follower.root(cx).unwrap();
16160    _ = follower.update(cx, {
16161        let update = pending_update.clone();
16162        let is_still_following = is_still_following.clone();
16163        let follower_edit_event_count = follower_edit_event_count.clone();
16164        |_, window, cx| {
16165            cx.subscribe_in(
16166                &leader_entity,
16167                window,
16168                move |_, leader, event, window, cx| {
16169                    leader.read(cx).add_event_to_update_proto(
16170                        event,
16171                        &mut update.borrow_mut(),
16172                        window,
16173                        cx,
16174                    );
16175                },
16176            )
16177            .detach();
16178
16179            cx.subscribe_in(
16180                &follower_entity,
16181                window,
16182                move |_, _, event: &EditorEvent, _window, _cx| {
16183                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16184                        *is_still_following.borrow_mut() = false;
16185                    }
16186
16187                    if let EditorEvent::BufferEdited = event {
16188                        *follower_edit_event_count.borrow_mut() += 1;
16189                    }
16190                },
16191            )
16192            .detach();
16193        }
16194    });
16195
16196    // Update the selections only
16197    _ = leader.update(cx, |leader, window, cx| {
16198        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16199            s.select_ranges([1..1])
16200        });
16201    });
16202    follower
16203        .update(cx, |follower, window, cx| {
16204            follower.apply_update_proto(
16205                &project,
16206                pending_update.borrow_mut().take().unwrap(),
16207                window,
16208                cx,
16209            )
16210        })
16211        .unwrap()
16212        .await
16213        .unwrap();
16214    _ = follower.update(cx, |follower, _, cx| {
16215        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
16216    });
16217    assert!(*is_still_following.borrow());
16218    assert_eq!(*follower_edit_event_count.borrow(), 0);
16219
16220    // Update the scroll position only
16221    _ = leader.update(cx, |leader, window, cx| {
16222        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16223    });
16224    follower
16225        .update(cx, |follower, window, cx| {
16226            follower.apply_update_proto(
16227                &project,
16228                pending_update.borrow_mut().take().unwrap(),
16229                window,
16230                cx,
16231            )
16232        })
16233        .unwrap()
16234        .await
16235        .unwrap();
16236    assert_eq!(
16237        follower
16238            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16239            .unwrap(),
16240        gpui::Point::new(1.5, 3.5)
16241    );
16242    assert!(*is_still_following.borrow());
16243    assert_eq!(*follower_edit_event_count.borrow(), 0);
16244
16245    // Update the selections and scroll position. The follower's scroll position is updated
16246    // via autoscroll, not via the leader's exact scroll position.
16247    _ = leader.update(cx, |leader, window, cx| {
16248        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16249            s.select_ranges([0..0])
16250        });
16251        leader.request_autoscroll(Autoscroll::newest(), cx);
16252        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16253    });
16254    follower
16255        .update(cx, |follower, window, cx| {
16256            follower.apply_update_proto(
16257                &project,
16258                pending_update.borrow_mut().take().unwrap(),
16259                window,
16260                cx,
16261            )
16262        })
16263        .unwrap()
16264        .await
16265        .unwrap();
16266    _ = follower.update(cx, |follower, _, cx| {
16267        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16268        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
16269    });
16270    assert!(*is_still_following.borrow());
16271
16272    // Creating a pending selection that precedes another selection
16273    _ = leader.update(cx, |leader, window, cx| {
16274        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16275            s.select_ranges([1..1])
16276        });
16277        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16278    });
16279    follower
16280        .update(cx, |follower, window, cx| {
16281            follower.apply_update_proto(
16282                &project,
16283                pending_update.borrow_mut().take().unwrap(),
16284                window,
16285                cx,
16286            )
16287        })
16288        .unwrap()
16289        .await
16290        .unwrap();
16291    _ = follower.update(cx, |follower, _, cx| {
16292        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
16293    });
16294    assert!(*is_still_following.borrow());
16295
16296    // Extend the pending selection so that it surrounds another selection
16297    _ = leader.update(cx, |leader, window, cx| {
16298        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16299    });
16300    follower
16301        .update(cx, |follower, window, cx| {
16302            follower.apply_update_proto(
16303                &project,
16304                pending_update.borrow_mut().take().unwrap(),
16305                window,
16306                cx,
16307            )
16308        })
16309        .unwrap()
16310        .await
16311        .unwrap();
16312    _ = follower.update(cx, |follower, _, cx| {
16313        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
16314    });
16315
16316    // Scrolling locally breaks the follow
16317    _ = follower.update(cx, |follower, window, cx| {
16318        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16319        follower.set_scroll_anchor(
16320            ScrollAnchor {
16321                anchor: top_anchor,
16322                offset: gpui::Point::new(0.0, 0.5),
16323            },
16324            window,
16325            cx,
16326        );
16327    });
16328    assert!(!(*is_still_following.borrow()));
16329}
16330
16331#[gpui::test]
16332async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16333    init_test(cx, |_| {});
16334
16335    let fs = FakeFs::new(cx.executor());
16336    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16337    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16338    let pane = workspace
16339        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16340        .unwrap();
16341
16342    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16343
16344    let leader = pane.update_in(cx, |_, window, cx| {
16345        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16346        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16347    });
16348
16349    // Start following the editor when it has no excerpts.
16350    let mut state_message =
16351        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16352    let workspace_entity = workspace.root(cx).unwrap();
16353    let follower_1 = cx
16354        .update_window(*workspace.deref(), |_, window, cx| {
16355            Editor::from_state_proto(
16356                workspace_entity,
16357                ViewId {
16358                    creator: CollaboratorId::PeerId(PeerId::default()),
16359                    id: 0,
16360                },
16361                &mut state_message,
16362                window,
16363                cx,
16364            )
16365        })
16366        .unwrap()
16367        .unwrap()
16368        .await
16369        .unwrap();
16370
16371    let update_message = Rc::new(RefCell::new(None));
16372    follower_1.update_in(cx, {
16373        let update = update_message.clone();
16374        |_, window, cx| {
16375            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16376                leader.read(cx).add_event_to_update_proto(
16377                    event,
16378                    &mut update.borrow_mut(),
16379                    window,
16380                    cx,
16381                );
16382            })
16383            .detach();
16384        }
16385    });
16386
16387    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16388        (
16389            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16390            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16391        )
16392    });
16393
16394    // Insert some excerpts.
16395    leader.update(cx, |leader, cx| {
16396        leader.buffer.update(cx, |multibuffer, cx| {
16397            multibuffer.set_excerpts_for_path(
16398                PathKey::namespaced(1, "b.txt".into()),
16399                buffer_1.clone(),
16400                vec![
16401                    Point::row_range(0..3),
16402                    Point::row_range(1..6),
16403                    Point::row_range(12..15),
16404                ],
16405                0,
16406                cx,
16407            );
16408            multibuffer.set_excerpts_for_path(
16409                PathKey::namespaced(1, "a.txt".into()),
16410                buffer_2.clone(),
16411                vec![Point::row_range(0..6), Point::row_range(8..12)],
16412                0,
16413                cx,
16414            );
16415        });
16416    });
16417
16418    // Apply the update of adding the excerpts.
16419    follower_1
16420        .update_in(cx, |follower, window, cx| {
16421            follower.apply_update_proto(
16422                &project,
16423                update_message.borrow().clone().unwrap(),
16424                window,
16425                cx,
16426            )
16427        })
16428        .await
16429        .unwrap();
16430    assert_eq!(
16431        follower_1.update(cx, |editor, cx| editor.text(cx)),
16432        leader.update(cx, |editor, cx| editor.text(cx))
16433    );
16434    update_message.borrow_mut().take();
16435
16436    // Start following separately after it already has excerpts.
16437    let mut state_message =
16438        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16439    let workspace_entity = workspace.root(cx).unwrap();
16440    let follower_2 = cx
16441        .update_window(*workspace.deref(), |_, window, cx| {
16442            Editor::from_state_proto(
16443                workspace_entity,
16444                ViewId {
16445                    creator: CollaboratorId::PeerId(PeerId::default()),
16446                    id: 0,
16447                },
16448                &mut state_message,
16449                window,
16450                cx,
16451            )
16452        })
16453        .unwrap()
16454        .unwrap()
16455        .await
16456        .unwrap();
16457    assert_eq!(
16458        follower_2.update(cx, |editor, cx| editor.text(cx)),
16459        leader.update(cx, |editor, cx| editor.text(cx))
16460    );
16461
16462    // Remove some excerpts.
16463    leader.update(cx, |leader, cx| {
16464        leader.buffer.update(cx, |multibuffer, cx| {
16465            let excerpt_ids = multibuffer.excerpt_ids();
16466            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16467            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16468        });
16469    });
16470
16471    // Apply the update of removing the excerpts.
16472    follower_1
16473        .update_in(cx, |follower, window, cx| {
16474            follower.apply_update_proto(
16475                &project,
16476                update_message.borrow().clone().unwrap(),
16477                window,
16478                cx,
16479            )
16480        })
16481        .await
16482        .unwrap();
16483    follower_2
16484        .update_in(cx, |follower, window, cx| {
16485            follower.apply_update_proto(
16486                &project,
16487                update_message.borrow().clone().unwrap(),
16488                window,
16489                cx,
16490            )
16491        })
16492        .await
16493        .unwrap();
16494    update_message.borrow_mut().take();
16495    assert_eq!(
16496        follower_1.update(cx, |editor, cx| editor.text(cx)),
16497        leader.update(cx, |editor, cx| editor.text(cx))
16498    );
16499}
16500
16501#[gpui::test]
16502async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16503    init_test(cx, |_| {});
16504
16505    let mut cx = EditorTestContext::new(cx).await;
16506    let lsp_store =
16507        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
16508
16509    cx.set_state(indoc! {"
16510        ˇfn func(abc def: i32) -> u32 {
16511        }
16512    "});
16513
16514    cx.update(|_, cx| {
16515        lsp_store.update(cx, |lsp_store, cx| {
16516            lsp_store
16517                .update_diagnostics(
16518                    LanguageServerId(0),
16519                    lsp::PublishDiagnosticsParams {
16520                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
16521                        version: None,
16522                        diagnostics: vec![
16523                            lsp::Diagnostic {
16524                                range: lsp::Range::new(
16525                                    lsp::Position::new(0, 11),
16526                                    lsp::Position::new(0, 12),
16527                                ),
16528                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16529                                ..Default::default()
16530                            },
16531                            lsp::Diagnostic {
16532                                range: lsp::Range::new(
16533                                    lsp::Position::new(0, 12),
16534                                    lsp::Position::new(0, 15),
16535                                ),
16536                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16537                                ..Default::default()
16538                            },
16539                            lsp::Diagnostic {
16540                                range: lsp::Range::new(
16541                                    lsp::Position::new(0, 25),
16542                                    lsp::Position::new(0, 28),
16543                                ),
16544                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16545                                ..Default::default()
16546                            },
16547                        ],
16548                    },
16549                    None,
16550                    DiagnosticSourceKind::Pushed,
16551                    &[],
16552                    cx,
16553                )
16554                .unwrap()
16555        });
16556    });
16557
16558    executor.run_until_parked();
16559
16560    cx.update_editor(|editor, window, cx| {
16561        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16562    });
16563
16564    cx.assert_editor_state(indoc! {"
16565        fn func(abc def: i32) -> ˇu32 {
16566        }
16567    "});
16568
16569    cx.update_editor(|editor, window, cx| {
16570        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16571    });
16572
16573    cx.assert_editor_state(indoc! {"
16574        fn func(abc ˇdef: i32) -> u32 {
16575        }
16576    "});
16577
16578    cx.update_editor(|editor, window, cx| {
16579        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16580    });
16581
16582    cx.assert_editor_state(indoc! {"
16583        fn func(abcˇ def: i32) -> u32 {
16584        }
16585    "});
16586
16587    cx.update_editor(|editor, window, cx| {
16588        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16589    });
16590
16591    cx.assert_editor_state(indoc! {"
16592        fn func(abc def: i32) -> ˇu32 {
16593        }
16594    "});
16595}
16596
16597#[gpui::test]
16598async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16599    init_test(cx, |_| {});
16600
16601    let mut cx = EditorTestContext::new(cx).await;
16602
16603    let diff_base = r#"
16604        use some::mod;
16605
16606        const A: u32 = 42;
16607
16608        fn main() {
16609            println!("hello");
16610
16611            println!("world");
16612        }
16613        "#
16614    .unindent();
16615
16616    // Edits are modified, removed, modified, added
16617    cx.set_state(
16618        &r#"
16619        use some::modified;
16620
16621        ˇ
16622        fn main() {
16623            println!("hello there");
16624
16625            println!("around the");
16626            println!("world");
16627        }
16628        "#
16629        .unindent(),
16630    );
16631
16632    cx.set_head_text(&diff_base);
16633    executor.run_until_parked();
16634
16635    cx.update_editor(|editor, window, cx| {
16636        //Wrap around the bottom of the buffer
16637        for _ in 0..3 {
16638            editor.go_to_next_hunk(&GoToHunk, window, cx);
16639        }
16640    });
16641
16642    cx.assert_editor_state(
16643        &r#"
16644        ˇuse some::modified;
16645
16646
16647        fn main() {
16648            println!("hello there");
16649
16650            println!("around the");
16651            println!("world");
16652        }
16653        "#
16654        .unindent(),
16655    );
16656
16657    cx.update_editor(|editor, window, cx| {
16658        //Wrap around the top of the buffer
16659        for _ in 0..2 {
16660            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16661        }
16662    });
16663
16664    cx.assert_editor_state(
16665        &r#"
16666        use some::modified;
16667
16668
16669        fn main() {
16670        ˇ    println!("hello there");
16671
16672            println!("around the");
16673            println!("world");
16674        }
16675        "#
16676        .unindent(),
16677    );
16678
16679    cx.update_editor(|editor, window, cx| {
16680        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16681    });
16682
16683    cx.assert_editor_state(
16684        &r#"
16685        use some::modified;
16686
16687        ˇ
16688        fn main() {
16689            println!("hello there");
16690
16691            println!("around the");
16692            println!("world");
16693        }
16694        "#
16695        .unindent(),
16696    );
16697
16698    cx.update_editor(|editor, window, cx| {
16699        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16700    });
16701
16702    cx.assert_editor_state(
16703        &r#"
16704        ˇuse some::modified;
16705
16706
16707        fn main() {
16708            println!("hello there");
16709
16710            println!("around the");
16711            println!("world");
16712        }
16713        "#
16714        .unindent(),
16715    );
16716
16717    cx.update_editor(|editor, window, cx| {
16718        for _ in 0..2 {
16719            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16720        }
16721    });
16722
16723    cx.assert_editor_state(
16724        &r#"
16725        use some::modified;
16726
16727
16728        fn main() {
16729        ˇ    println!("hello there");
16730
16731            println!("around the");
16732            println!("world");
16733        }
16734        "#
16735        .unindent(),
16736    );
16737
16738    cx.update_editor(|editor, window, cx| {
16739        editor.fold(&Fold, window, cx);
16740    });
16741
16742    cx.update_editor(|editor, window, cx| {
16743        editor.go_to_next_hunk(&GoToHunk, window, cx);
16744    });
16745
16746    cx.assert_editor_state(
16747        &r#"
16748        ˇuse some::modified;
16749
16750
16751        fn main() {
16752            println!("hello there");
16753
16754            println!("around the");
16755            println!("world");
16756        }
16757        "#
16758        .unindent(),
16759    );
16760}
16761
16762#[test]
16763fn test_split_words() {
16764    fn split(text: &str) -> Vec<&str> {
16765        split_words(text).collect()
16766    }
16767
16768    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
16769    assert_eq!(split("hello_world"), &["hello_", "world"]);
16770    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
16771    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
16772    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
16773    assert_eq!(split("helloworld"), &["helloworld"]);
16774
16775    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
16776}
16777
16778#[gpui::test]
16779async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
16780    init_test(cx, |_| {});
16781
16782    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
16783    let mut assert = |before, after| {
16784        let _state_context = cx.set_state(before);
16785        cx.run_until_parked();
16786        cx.update_editor(|editor, window, cx| {
16787            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
16788        });
16789        cx.run_until_parked();
16790        cx.assert_editor_state(after);
16791    };
16792
16793    // Outside bracket jumps to outside of matching bracket
16794    assert("console.logˇ(var);", "console.log(var)ˇ;");
16795    assert("console.log(var)ˇ;", "console.logˇ(var);");
16796
16797    // Inside bracket jumps to inside of matching bracket
16798    assert("console.log(ˇvar);", "console.log(varˇ);");
16799    assert("console.log(varˇ);", "console.log(ˇvar);");
16800
16801    // When outside a bracket and inside, favor jumping to the inside bracket
16802    assert(
16803        "console.log('foo', [1, 2, 3]ˇ);",
16804        "console.log(ˇ'foo', [1, 2, 3]);",
16805    );
16806    assert(
16807        "console.log(ˇ'foo', [1, 2, 3]);",
16808        "console.log('foo', [1, 2, 3]ˇ);",
16809    );
16810
16811    // Bias forward if two options are equally likely
16812    assert(
16813        "let result = curried_fun()ˇ();",
16814        "let result = curried_fun()()ˇ;",
16815    );
16816
16817    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
16818    assert(
16819        indoc! {"
16820            function test() {
16821                console.log('test')ˇ
16822            }"},
16823        indoc! {"
16824            function test() {
16825                console.logˇ('test')
16826            }"},
16827    );
16828}
16829
16830#[gpui::test]
16831async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
16832    init_test(cx, |_| {});
16833
16834    let fs = FakeFs::new(cx.executor());
16835    fs.insert_tree(
16836        path!("/a"),
16837        json!({
16838            "main.rs": "fn main() { let a = 5; }",
16839            "other.rs": "// Test file",
16840        }),
16841    )
16842    .await;
16843    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16844
16845    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16846    language_registry.add(Arc::new(Language::new(
16847        LanguageConfig {
16848            name: "Rust".into(),
16849            matcher: LanguageMatcher {
16850                path_suffixes: vec!["rs".to_string()],
16851                ..Default::default()
16852            },
16853            brackets: BracketPairConfig {
16854                pairs: vec![BracketPair {
16855                    start: "{".to_string(),
16856                    end: "}".to_string(),
16857                    close: true,
16858                    surround: true,
16859                    newline: true,
16860                }],
16861                disabled_scopes_by_bracket_ix: Vec::new(),
16862            },
16863            ..Default::default()
16864        },
16865        Some(tree_sitter_rust::LANGUAGE.into()),
16866    )));
16867    let mut fake_servers = language_registry.register_fake_lsp(
16868        "Rust",
16869        FakeLspAdapter {
16870            capabilities: lsp::ServerCapabilities {
16871                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
16872                    first_trigger_character: "{".to_string(),
16873                    more_trigger_character: None,
16874                }),
16875                ..Default::default()
16876            },
16877            ..Default::default()
16878        },
16879    );
16880
16881    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16882
16883    let cx = &mut VisualTestContext::from_window(*workspace, cx);
16884
16885    let worktree_id = workspace
16886        .update(cx, |workspace, _, cx| {
16887            workspace.project().update(cx, |project, cx| {
16888                project.worktrees(cx).next().unwrap().read(cx).id()
16889            })
16890        })
16891        .unwrap();
16892
16893    let buffer = project
16894        .update(cx, |project, cx| {
16895            project.open_local_buffer(path!("/a/main.rs"), cx)
16896        })
16897        .await
16898        .unwrap();
16899    let editor_handle = workspace
16900        .update(cx, |workspace, window, cx| {
16901            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
16902        })
16903        .unwrap()
16904        .await
16905        .unwrap()
16906        .downcast::<Editor>()
16907        .unwrap();
16908
16909    cx.executor().start_waiting();
16910    let fake_server = fake_servers.next().await.unwrap();
16911
16912    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
16913        |params, _| async move {
16914            assert_eq!(
16915                params.text_document_position.text_document.uri,
16916                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
16917            );
16918            assert_eq!(
16919                params.text_document_position.position,
16920                lsp::Position::new(0, 21),
16921            );
16922
16923            Ok(Some(vec![lsp::TextEdit {
16924                new_text: "]".to_string(),
16925                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16926            }]))
16927        },
16928    );
16929
16930    editor_handle.update_in(cx, |editor, window, cx| {
16931        window.focus(&editor.focus_handle(cx));
16932        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16933            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
16934        });
16935        editor.handle_input("{", window, cx);
16936    });
16937
16938    cx.executor().run_until_parked();
16939
16940    buffer.update(cx, |buffer, _| {
16941        assert_eq!(
16942            buffer.text(),
16943            "fn main() { let a = {5}; }",
16944            "No extra braces from on type formatting should appear in the buffer"
16945        )
16946    });
16947}
16948
16949#[gpui::test(iterations = 20, seeds(31))]
16950async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
16951    init_test(cx, |_| {});
16952
16953    let mut cx = EditorLspTestContext::new_rust(
16954        lsp::ServerCapabilities {
16955            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
16956                first_trigger_character: ".".to_string(),
16957                more_trigger_character: None,
16958            }),
16959            ..Default::default()
16960        },
16961        cx,
16962    )
16963    .await;
16964
16965    cx.update_buffer(|buffer, _| {
16966        // This causes autoindent to be async.
16967        buffer.set_sync_parse_timeout(Duration::ZERO)
16968    });
16969
16970    cx.set_state("fn c() {\n    d()ˇ\n}\n");
16971    cx.simulate_keystroke("\n");
16972    cx.run_until_parked();
16973
16974    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
16975    let mut request =
16976        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
16977            let buffer_cloned = buffer_cloned.clone();
16978            async move {
16979                buffer_cloned.update(&mut cx, |buffer, _| {
16980                    assert_eq!(
16981                        buffer.text(),
16982                        "fn c() {\n    d()\n        .\n}\n",
16983                        "OnTypeFormatting should triggered after autoindent applied"
16984                    )
16985                })?;
16986
16987                Ok(Some(vec![]))
16988            }
16989        });
16990
16991    cx.simulate_keystroke(".");
16992    cx.run_until_parked();
16993
16994    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
16995    assert!(request.next().await.is_some());
16996    request.close();
16997    assert!(request.next().await.is_none());
16998}
16999
17000#[gpui::test]
17001async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17002    init_test(cx, |_| {});
17003
17004    let fs = FakeFs::new(cx.executor());
17005    fs.insert_tree(
17006        path!("/a"),
17007        json!({
17008            "main.rs": "fn main() { let a = 5; }",
17009            "other.rs": "// Test file",
17010        }),
17011    )
17012    .await;
17013
17014    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17015
17016    let server_restarts = Arc::new(AtomicUsize::new(0));
17017    let closure_restarts = Arc::clone(&server_restarts);
17018    let language_server_name = "test language server";
17019    let language_name: LanguageName = "Rust".into();
17020
17021    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17022    language_registry.add(Arc::new(Language::new(
17023        LanguageConfig {
17024            name: language_name.clone(),
17025            matcher: LanguageMatcher {
17026                path_suffixes: vec!["rs".to_string()],
17027                ..Default::default()
17028            },
17029            ..Default::default()
17030        },
17031        Some(tree_sitter_rust::LANGUAGE.into()),
17032    )));
17033    let mut fake_servers = language_registry.register_fake_lsp(
17034        "Rust",
17035        FakeLspAdapter {
17036            name: language_server_name,
17037            initialization_options: Some(json!({
17038                "testOptionValue": true
17039            })),
17040            initializer: Some(Box::new(move |fake_server| {
17041                let task_restarts = Arc::clone(&closure_restarts);
17042                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17043                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17044                    futures::future::ready(Ok(()))
17045                });
17046            })),
17047            ..Default::default()
17048        },
17049    );
17050
17051    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17052    let _buffer = project
17053        .update(cx, |project, cx| {
17054            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17055        })
17056        .await
17057        .unwrap();
17058    let _fake_server = fake_servers.next().await.unwrap();
17059    update_test_language_settings(cx, |language_settings| {
17060        language_settings.languages.0.insert(
17061            language_name.clone().0,
17062            LanguageSettingsContent {
17063                tab_size: NonZeroU32::new(8),
17064                ..Default::default()
17065            },
17066        );
17067    });
17068    cx.executor().run_until_parked();
17069    assert_eq!(
17070        server_restarts.load(atomic::Ordering::Acquire),
17071        0,
17072        "Should not restart LSP server on an unrelated change"
17073    );
17074
17075    update_test_project_settings(cx, |project_settings| {
17076        project_settings.lsp.insert(
17077            "Some other server name".into(),
17078            LspSettings {
17079                binary: None,
17080                settings: None,
17081                initialization_options: Some(json!({
17082                    "some other init value": false
17083                })),
17084                enable_lsp_tasks: false,
17085                fetch: None,
17086            },
17087        );
17088    });
17089    cx.executor().run_until_parked();
17090    assert_eq!(
17091        server_restarts.load(atomic::Ordering::Acquire),
17092        0,
17093        "Should not restart LSP server on an unrelated LSP settings change"
17094    );
17095
17096    update_test_project_settings(cx, |project_settings| {
17097        project_settings.lsp.insert(
17098            language_server_name.into(),
17099            LspSettings {
17100                binary: None,
17101                settings: None,
17102                initialization_options: Some(json!({
17103                    "anotherInitValue": false
17104                })),
17105                enable_lsp_tasks: false,
17106                fetch: None,
17107            },
17108        );
17109    });
17110    cx.executor().run_until_parked();
17111    assert_eq!(
17112        server_restarts.load(atomic::Ordering::Acquire),
17113        1,
17114        "Should restart LSP server on a related LSP settings change"
17115    );
17116
17117    update_test_project_settings(cx, |project_settings| {
17118        project_settings.lsp.insert(
17119            language_server_name.into(),
17120            LspSettings {
17121                binary: None,
17122                settings: None,
17123                initialization_options: Some(json!({
17124                    "anotherInitValue": false
17125                })),
17126                enable_lsp_tasks: false,
17127                fetch: None,
17128            },
17129        );
17130    });
17131    cx.executor().run_until_parked();
17132    assert_eq!(
17133        server_restarts.load(atomic::Ordering::Acquire),
17134        1,
17135        "Should not restart LSP server on a related LSP settings change that is the same"
17136    );
17137
17138    update_test_project_settings(cx, |project_settings| {
17139        project_settings.lsp.insert(
17140            language_server_name.into(),
17141            LspSettings {
17142                binary: None,
17143                settings: None,
17144                initialization_options: None,
17145                enable_lsp_tasks: false,
17146                fetch: None,
17147            },
17148        );
17149    });
17150    cx.executor().run_until_parked();
17151    assert_eq!(
17152        server_restarts.load(atomic::Ordering::Acquire),
17153        2,
17154        "Should restart LSP server on another related LSP settings change"
17155    );
17156}
17157
17158#[gpui::test]
17159async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17160    init_test(cx, |_| {});
17161
17162    let mut cx = EditorLspTestContext::new_rust(
17163        lsp::ServerCapabilities {
17164            completion_provider: Some(lsp::CompletionOptions {
17165                trigger_characters: Some(vec![".".to_string()]),
17166                resolve_provider: Some(true),
17167                ..Default::default()
17168            }),
17169            ..Default::default()
17170        },
17171        cx,
17172    )
17173    .await;
17174
17175    cx.set_state("fn main() { let a = 2ˇ; }");
17176    cx.simulate_keystroke(".");
17177    let completion_item = lsp::CompletionItem {
17178        label: "some".into(),
17179        kind: Some(lsp::CompletionItemKind::SNIPPET),
17180        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17181        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17182            kind: lsp::MarkupKind::Markdown,
17183            value: "```rust\nSome(2)\n```".to_string(),
17184        })),
17185        deprecated: Some(false),
17186        sort_text: Some("fffffff2".to_string()),
17187        filter_text: Some("some".to_string()),
17188        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17189        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17190            range: lsp::Range {
17191                start: lsp::Position {
17192                    line: 0,
17193                    character: 22,
17194                },
17195                end: lsp::Position {
17196                    line: 0,
17197                    character: 22,
17198                },
17199            },
17200            new_text: "Some(2)".to_string(),
17201        })),
17202        additional_text_edits: Some(vec![lsp::TextEdit {
17203            range: lsp::Range {
17204                start: lsp::Position {
17205                    line: 0,
17206                    character: 20,
17207                },
17208                end: lsp::Position {
17209                    line: 0,
17210                    character: 22,
17211                },
17212            },
17213            new_text: "".to_string(),
17214        }]),
17215        ..Default::default()
17216    };
17217
17218    let closure_completion_item = completion_item.clone();
17219    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17220        let task_completion_item = closure_completion_item.clone();
17221        async move {
17222            Ok(Some(lsp::CompletionResponse::Array(vec![
17223                task_completion_item,
17224            ])))
17225        }
17226    });
17227
17228    request.next().await;
17229
17230    cx.condition(|editor, _| editor.context_menu_visible())
17231        .await;
17232    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17233        editor
17234            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17235            .unwrap()
17236    });
17237    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17238
17239    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17240        let task_completion_item = completion_item.clone();
17241        async move { Ok(task_completion_item) }
17242    })
17243    .next()
17244    .await
17245    .unwrap();
17246    apply_additional_edits.await.unwrap();
17247    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17248}
17249
17250#[gpui::test]
17251async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17252    init_test(cx, |_| {});
17253
17254    let mut cx = EditorLspTestContext::new_rust(
17255        lsp::ServerCapabilities {
17256            completion_provider: Some(lsp::CompletionOptions {
17257                trigger_characters: Some(vec![".".to_string()]),
17258                resolve_provider: Some(true),
17259                ..Default::default()
17260            }),
17261            ..Default::default()
17262        },
17263        cx,
17264    )
17265    .await;
17266
17267    cx.set_state("fn main() { let a = 2ˇ; }");
17268    cx.simulate_keystroke(".");
17269
17270    let item1 = lsp::CompletionItem {
17271        label: "method id()".to_string(),
17272        filter_text: Some("id".to_string()),
17273        detail: None,
17274        documentation: None,
17275        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17276            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17277            new_text: ".id".to_string(),
17278        })),
17279        ..lsp::CompletionItem::default()
17280    };
17281
17282    let item2 = lsp::CompletionItem {
17283        label: "other".to_string(),
17284        filter_text: Some("other".to_string()),
17285        detail: None,
17286        documentation: None,
17287        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17288            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17289            new_text: ".other".to_string(),
17290        })),
17291        ..lsp::CompletionItem::default()
17292    };
17293
17294    let item1 = item1.clone();
17295    cx.set_request_handler::<lsp::request::Completion, _, _>({
17296        let item1 = item1.clone();
17297        move |_, _, _| {
17298            let item1 = item1.clone();
17299            let item2 = item2.clone();
17300            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17301        }
17302    })
17303    .next()
17304    .await;
17305
17306    cx.condition(|editor, _| editor.context_menu_visible())
17307        .await;
17308    cx.update_editor(|editor, _, _| {
17309        let context_menu = editor.context_menu.borrow_mut();
17310        let context_menu = context_menu
17311            .as_ref()
17312            .expect("Should have the context menu deployed");
17313        match context_menu {
17314            CodeContextMenu::Completions(completions_menu) => {
17315                let completions = completions_menu.completions.borrow_mut();
17316                assert_eq!(
17317                    completions
17318                        .iter()
17319                        .map(|completion| &completion.label.text)
17320                        .collect::<Vec<_>>(),
17321                    vec!["method id()", "other"]
17322                )
17323            }
17324            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17325        }
17326    });
17327
17328    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17329        let item1 = item1.clone();
17330        move |_, item_to_resolve, _| {
17331            let item1 = item1.clone();
17332            async move {
17333                if item1 == item_to_resolve {
17334                    Ok(lsp::CompletionItem {
17335                        label: "method id()".to_string(),
17336                        filter_text: Some("id".to_string()),
17337                        detail: Some("Now resolved!".to_string()),
17338                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17339                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17340                            range: lsp::Range::new(
17341                                lsp::Position::new(0, 22),
17342                                lsp::Position::new(0, 22),
17343                            ),
17344                            new_text: ".id".to_string(),
17345                        })),
17346                        ..lsp::CompletionItem::default()
17347                    })
17348                } else {
17349                    Ok(item_to_resolve)
17350                }
17351            }
17352        }
17353    })
17354    .next()
17355    .await
17356    .unwrap();
17357    cx.run_until_parked();
17358
17359    cx.update_editor(|editor, window, cx| {
17360        editor.context_menu_next(&Default::default(), window, cx);
17361    });
17362
17363    cx.update_editor(|editor, _, _| {
17364        let context_menu = editor.context_menu.borrow_mut();
17365        let context_menu = context_menu
17366            .as_ref()
17367            .expect("Should have the context menu deployed");
17368        match context_menu {
17369            CodeContextMenu::Completions(completions_menu) => {
17370                let completions = completions_menu.completions.borrow_mut();
17371                assert_eq!(
17372                    completions
17373                        .iter()
17374                        .map(|completion| &completion.label.text)
17375                        .collect::<Vec<_>>(),
17376                    vec!["method id() Now resolved!", "other"],
17377                    "Should update first completion label, but not second as the filter text did not match."
17378                );
17379            }
17380            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17381        }
17382    });
17383}
17384
17385#[gpui::test]
17386async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17387    init_test(cx, |_| {});
17388    let mut cx = EditorLspTestContext::new_rust(
17389        lsp::ServerCapabilities {
17390            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17391            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17392            completion_provider: Some(lsp::CompletionOptions {
17393                resolve_provider: Some(true),
17394                ..Default::default()
17395            }),
17396            ..Default::default()
17397        },
17398        cx,
17399    )
17400    .await;
17401    cx.set_state(indoc! {"
17402        struct TestStruct {
17403            field: i32
17404        }
17405
17406        fn mainˇ() {
17407            let unused_var = 42;
17408            let test_struct = TestStruct { field: 42 };
17409        }
17410    "});
17411    let symbol_range = cx.lsp_range(indoc! {"
17412        struct TestStruct {
17413            field: i32
17414        }
17415
17416        «fn main»() {
17417            let unused_var = 42;
17418            let test_struct = TestStruct { field: 42 };
17419        }
17420    "});
17421    let mut hover_requests =
17422        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17423            Ok(Some(lsp::Hover {
17424                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17425                    kind: lsp::MarkupKind::Markdown,
17426                    value: "Function documentation".to_string(),
17427                }),
17428                range: Some(symbol_range),
17429            }))
17430        });
17431
17432    // Case 1: Test that code action menu hide hover popover
17433    cx.dispatch_action(Hover);
17434    hover_requests.next().await;
17435    cx.condition(|editor, _| editor.hover_state.visible()).await;
17436    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17437        move |_, _, _| async move {
17438            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17439                lsp::CodeAction {
17440                    title: "Remove unused variable".to_string(),
17441                    kind: Some(CodeActionKind::QUICKFIX),
17442                    edit: Some(lsp::WorkspaceEdit {
17443                        changes: Some(
17444                            [(
17445                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17446                                vec![lsp::TextEdit {
17447                                    range: lsp::Range::new(
17448                                        lsp::Position::new(5, 4),
17449                                        lsp::Position::new(5, 27),
17450                                    ),
17451                                    new_text: "".to_string(),
17452                                }],
17453                            )]
17454                            .into_iter()
17455                            .collect(),
17456                        ),
17457                        ..Default::default()
17458                    }),
17459                    ..Default::default()
17460                },
17461            )]))
17462        },
17463    );
17464    cx.update_editor(|editor, window, cx| {
17465        editor.toggle_code_actions(
17466            &ToggleCodeActions {
17467                deployed_from: None,
17468                quick_launch: false,
17469            },
17470            window,
17471            cx,
17472        );
17473    });
17474    code_action_requests.next().await;
17475    cx.run_until_parked();
17476    cx.condition(|editor, _| editor.context_menu_visible())
17477        .await;
17478    cx.update_editor(|editor, _, _| {
17479        assert!(
17480            !editor.hover_state.visible(),
17481            "Hover popover should be hidden when code action menu is shown"
17482        );
17483        // Hide code actions
17484        editor.context_menu.take();
17485    });
17486
17487    // Case 2: Test that code completions hide hover popover
17488    cx.dispatch_action(Hover);
17489    hover_requests.next().await;
17490    cx.condition(|editor, _| editor.hover_state.visible()).await;
17491    let counter = Arc::new(AtomicUsize::new(0));
17492    let mut completion_requests =
17493        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17494            let counter = counter.clone();
17495            async move {
17496                counter.fetch_add(1, atomic::Ordering::Release);
17497                Ok(Some(lsp::CompletionResponse::Array(vec![
17498                    lsp::CompletionItem {
17499                        label: "main".into(),
17500                        kind: Some(lsp::CompletionItemKind::FUNCTION),
17501                        detail: Some("() -> ()".to_string()),
17502                        ..Default::default()
17503                    },
17504                    lsp::CompletionItem {
17505                        label: "TestStruct".into(),
17506                        kind: Some(lsp::CompletionItemKind::STRUCT),
17507                        detail: Some("struct TestStruct".to_string()),
17508                        ..Default::default()
17509                    },
17510                ])))
17511            }
17512        });
17513    cx.update_editor(|editor, window, cx| {
17514        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
17515    });
17516    completion_requests.next().await;
17517    cx.condition(|editor, _| editor.context_menu_visible())
17518        .await;
17519    cx.update_editor(|editor, _, _| {
17520        assert!(
17521            !editor.hover_state.visible(),
17522            "Hover popover should be hidden when completion menu is shown"
17523        );
17524    });
17525}
17526
17527#[gpui::test]
17528async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
17529    init_test(cx, |_| {});
17530
17531    let mut cx = EditorLspTestContext::new_rust(
17532        lsp::ServerCapabilities {
17533            completion_provider: Some(lsp::CompletionOptions {
17534                trigger_characters: Some(vec![".".to_string()]),
17535                resolve_provider: Some(true),
17536                ..Default::default()
17537            }),
17538            ..Default::default()
17539        },
17540        cx,
17541    )
17542    .await;
17543
17544    cx.set_state("fn main() { let a = 2ˇ; }");
17545    cx.simulate_keystroke(".");
17546
17547    let unresolved_item_1 = lsp::CompletionItem {
17548        label: "id".to_string(),
17549        filter_text: Some("id".to_string()),
17550        detail: None,
17551        documentation: None,
17552        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17553            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17554            new_text: ".id".to_string(),
17555        })),
17556        ..lsp::CompletionItem::default()
17557    };
17558    let resolved_item_1 = lsp::CompletionItem {
17559        additional_text_edits: Some(vec![lsp::TextEdit {
17560            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17561            new_text: "!!".to_string(),
17562        }]),
17563        ..unresolved_item_1.clone()
17564    };
17565    let unresolved_item_2 = lsp::CompletionItem {
17566        label: "other".to_string(),
17567        filter_text: Some("other".to_string()),
17568        detail: None,
17569        documentation: None,
17570        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17571            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17572            new_text: ".other".to_string(),
17573        })),
17574        ..lsp::CompletionItem::default()
17575    };
17576    let resolved_item_2 = lsp::CompletionItem {
17577        additional_text_edits: Some(vec![lsp::TextEdit {
17578            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17579            new_text: "??".to_string(),
17580        }]),
17581        ..unresolved_item_2.clone()
17582    };
17583
17584    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
17585    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
17586    cx.lsp
17587        .server
17588        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17589            let unresolved_item_1 = unresolved_item_1.clone();
17590            let resolved_item_1 = resolved_item_1.clone();
17591            let unresolved_item_2 = unresolved_item_2.clone();
17592            let resolved_item_2 = resolved_item_2.clone();
17593            let resolve_requests_1 = resolve_requests_1.clone();
17594            let resolve_requests_2 = resolve_requests_2.clone();
17595            move |unresolved_request, _| {
17596                let unresolved_item_1 = unresolved_item_1.clone();
17597                let resolved_item_1 = resolved_item_1.clone();
17598                let unresolved_item_2 = unresolved_item_2.clone();
17599                let resolved_item_2 = resolved_item_2.clone();
17600                let resolve_requests_1 = resolve_requests_1.clone();
17601                let resolve_requests_2 = resolve_requests_2.clone();
17602                async move {
17603                    if unresolved_request == unresolved_item_1 {
17604                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
17605                        Ok(resolved_item_1.clone())
17606                    } else if unresolved_request == unresolved_item_2 {
17607                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
17608                        Ok(resolved_item_2.clone())
17609                    } else {
17610                        panic!("Unexpected completion item {unresolved_request:?}")
17611                    }
17612                }
17613            }
17614        })
17615        .detach();
17616
17617    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17618        let unresolved_item_1 = unresolved_item_1.clone();
17619        let unresolved_item_2 = unresolved_item_2.clone();
17620        async move {
17621            Ok(Some(lsp::CompletionResponse::Array(vec![
17622                unresolved_item_1,
17623                unresolved_item_2,
17624            ])))
17625        }
17626    })
17627    .next()
17628    .await;
17629
17630    cx.condition(|editor, _| editor.context_menu_visible())
17631        .await;
17632    cx.update_editor(|editor, _, _| {
17633        let context_menu = editor.context_menu.borrow_mut();
17634        let context_menu = context_menu
17635            .as_ref()
17636            .expect("Should have the context menu deployed");
17637        match context_menu {
17638            CodeContextMenu::Completions(completions_menu) => {
17639                let completions = completions_menu.completions.borrow_mut();
17640                assert_eq!(
17641                    completions
17642                        .iter()
17643                        .map(|completion| &completion.label.text)
17644                        .collect::<Vec<_>>(),
17645                    vec!["id", "other"]
17646                )
17647            }
17648            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17649        }
17650    });
17651    cx.run_until_parked();
17652
17653    cx.update_editor(|editor, window, cx| {
17654        editor.context_menu_next(&ContextMenuNext, window, cx);
17655    });
17656    cx.run_until_parked();
17657    cx.update_editor(|editor, window, cx| {
17658        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17659    });
17660    cx.run_until_parked();
17661    cx.update_editor(|editor, window, cx| {
17662        editor.context_menu_next(&ContextMenuNext, window, cx);
17663    });
17664    cx.run_until_parked();
17665    cx.update_editor(|editor, window, cx| {
17666        editor
17667            .compose_completion(&ComposeCompletion::default(), window, cx)
17668            .expect("No task returned")
17669    })
17670    .await
17671    .expect("Completion failed");
17672    cx.run_until_parked();
17673
17674    cx.update_editor(|editor, _, cx| {
17675        assert_eq!(
17676            resolve_requests_1.load(atomic::Ordering::Acquire),
17677            1,
17678            "Should always resolve once despite multiple selections"
17679        );
17680        assert_eq!(
17681            resolve_requests_2.load(atomic::Ordering::Acquire),
17682            1,
17683            "Should always resolve once after multiple selections and applying the completion"
17684        );
17685        assert_eq!(
17686            editor.text(cx),
17687            "fn main() { let a = ??.other; }",
17688            "Should use resolved data when applying the completion"
17689        );
17690    });
17691}
17692
17693#[gpui::test]
17694async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
17695    init_test(cx, |_| {});
17696
17697    let item_0 = lsp::CompletionItem {
17698        label: "abs".into(),
17699        insert_text: Some("abs".into()),
17700        data: Some(json!({ "very": "special"})),
17701        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
17702        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
17703            lsp::InsertReplaceEdit {
17704                new_text: "abs".to_string(),
17705                insert: lsp::Range::default(),
17706                replace: lsp::Range::default(),
17707            },
17708        )),
17709        ..lsp::CompletionItem::default()
17710    };
17711    let items = iter::once(item_0.clone())
17712        .chain((11..51).map(|i| lsp::CompletionItem {
17713            label: format!("item_{}", i),
17714            insert_text: Some(format!("item_{}", i)),
17715            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
17716            ..lsp::CompletionItem::default()
17717        }))
17718        .collect::<Vec<_>>();
17719
17720    let default_commit_characters = vec!["?".to_string()];
17721    let default_data = json!({ "default": "data"});
17722    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
17723    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
17724    let default_edit_range = lsp::Range {
17725        start: lsp::Position {
17726            line: 0,
17727            character: 5,
17728        },
17729        end: lsp::Position {
17730            line: 0,
17731            character: 5,
17732        },
17733    };
17734
17735    let mut cx = EditorLspTestContext::new_rust(
17736        lsp::ServerCapabilities {
17737            completion_provider: Some(lsp::CompletionOptions {
17738                trigger_characters: Some(vec![".".to_string()]),
17739                resolve_provider: Some(true),
17740                ..Default::default()
17741            }),
17742            ..Default::default()
17743        },
17744        cx,
17745    )
17746    .await;
17747
17748    cx.set_state("fn main() { let a = 2ˇ; }");
17749    cx.simulate_keystroke(".");
17750
17751    let completion_data = default_data.clone();
17752    let completion_characters = default_commit_characters.clone();
17753    let completion_items = items.clone();
17754    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17755        let default_data = completion_data.clone();
17756        let default_commit_characters = completion_characters.clone();
17757        let items = completion_items.clone();
17758        async move {
17759            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
17760                items,
17761                item_defaults: Some(lsp::CompletionListItemDefaults {
17762                    data: Some(default_data.clone()),
17763                    commit_characters: Some(default_commit_characters.clone()),
17764                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
17765                        default_edit_range,
17766                    )),
17767                    insert_text_format: Some(default_insert_text_format),
17768                    insert_text_mode: Some(default_insert_text_mode),
17769                }),
17770                ..lsp::CompletionList::default()
17771            })))
17772        }
17773    })
17774    .next()
17775    .await;
17776
17777    let resolved_items = Arc::new(Mutex::new(Vec::new()));
17778    cx.lsp
17779        .server
17780        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17781            let closure_resolved_items = resolved_items.clone();
17782            move |item_to_resolve, _| {
17783                let closure_resolved_items = closure_resolved_items.clone();
17784                async move {
17785                    closure_resolved_items.lock().push(item_to_resolve.clone());
17786                    Ok(item_to_resolve)
17787                }
17788            }
17789        })
17790        .detach();
17791
17792    cx.condition(|editor, _| editor.context_menu_visible())
17793        .await;
17794    cx.run_until_parked();
17795    cx.update_editor(|editor, _, _| {
17796        let menu = editor.context_menu.borrow_mut();
17797        match menu.as_ref().expect("should have the completions menu") {
17798            CodeContextMenu::Completions(completions_menu) => {
17799                assert_eq!(
17800                    completions_menu
17801                        .entries
17802                        .borrow()
17803                        .iter()
17804                        .map(|mat| mat.string.clone())
17805                        .collect::<Vec<String>>(),
17806                    items
17807                        .iter()
17808                        .map(|completion| completion.label.clone())
17809                        .collect::<Vec<String>>()
17810                );
17811            }
17812            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
17813        }
17814    });
17815    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
17816    // with 4 from the end.
17817    assert_eq!(
17818        *resolved_items.lock(),
17819        [&items[0..16], &items[items.len() - 4..items.len()]]
17820            .concat()
17821            .iter()
17822            .cloned()
17823            .map(|mut item| {
17824                if item.data.is_none() {
17825                    item.data = Some(default_data.clone());
17826                }
17827                item
17828            })
17829            .collect::<Vec<lsp::CompletionItem>>(),
17830        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
17831    );
17832    resolved_items.lock().clear();
17833
17834    cx.update_editor(|editor, window, cx| {
17835        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17836    });
17837    cx.run_until_parked();
17838    // Completions that have already been resolved are skipped.
17839    assert_eq!(
17840        *resolved_items.lock(),
17841        items[items.len() - 17..items.len() - 4]
17842            .iter()
17843            .cloned()
17844            .map(|mut item| {
17845                if item.data.is_none() {
17846                    item.data = Some(default_data.clone());
17847                }
17848                item
17849            })
17850            .collect::<Vec<lsp::CompletionItem>>()
17851    );
17852    resolved_items.lock().clear();
17853}
17854
17855#[gpui::test]
17856async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
17857    init_test(cx, |_| {});
17858
17859    let mut cx = EditorLspTestContext::new(
17860        Language::new(
17861            LanguageConfig {
17862                matcher: LanguageMatcher {
17863                    path_suffixes: vec!["jsx".into()],
17864                    ..Default::default()
17865                },
17866                overrides: [(
17867                    "element".into(),
17868                    LanguageConfigOverride {
17869                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
17870                        ..Default::default()
17871                    },
17872                )]
17873                .into_iter()
17874                .collect(),
17875                ..Default::default()
17876            },
17877            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
17878        )
17879        .with_override_query("(jsx_self_closing_element) @element")
17880        .unwrap(),
17881        lsp::ServerCapabilities {
17882            completion_provider: Some(lsp::CompletionOptions {
17883                trigger_characters: Some(vec![":".to_string()]),
17884                ..Default::default()
17885            }),
17886            ..Default::default()
17887        },
17888        cx,
17889    )
17890    .await;
17891
17892    cx.lsp
17893        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
17894            Ok(Some(lsp::CompletionResponse::Array(vec![
17895                lsp::CompletionItem {
17896                    label: "bg-blue".into(),
17897                    ..Default::default()
17898                },
17899                lsp::CompletionItem {
17900                    label: "bg-red".into(),
17901                    ..Default::default()
17902                },
17903                lsp::CompletionItem {
17904                    label: "bg-yellow".into(),
17905                    ..Default::default()
17906                },
17907            ])))
17908        });
17909
17910    cx.set_state(r#"<p class="bgˇ" />"#);
17911
17912    // Trigger completion when typing a dash, because the dash is an extra
17913    // word character in the 'element' scope, which contains the cursor.
17914    cx.simulate_keystroke("-");
17915    cx.executor().run_until_parked();
17916    cx.update_editor(|editor, _, _| {
17917        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17918        {
17919            assert_eq!(
17920                completion_menu_entries(menu),
17921                &["bg-blue", "bg-red", "bg-yellow"]
17922            );
17923        } else {
17924            panic!("expected completion menu to be open");
17925        }
17926    });
17927
17928    cx.simulate_keystroke("l");
17929    cx.executor().run_until_parked();
17930    cx.update_editor(|editor, _, _| {
17931        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17932        {
17933            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
17934        } else {
17935            panic!("expected completion menu to be open");
17936        }
17937    });
17938
17939    // When filtering completions, consider the character after the '-' to
17940    // be the start of a subword.
17941    cx.set_state(r#"<p class="yelˇ" />"#);
17942    cx.simulate_keystroke("l");
17943    cx.executor().run_until_parked();
17944    cx.update_editor(|editor, _, _| {
17945        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17946        {
17947            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
17948        } else {
17949            panic!("expected completion menu to be open");
17950        }
17951    });
17952}
17953
17954fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
17955    let entries = menu.entries.borrow();
17956    entries.iter().map(|mat| mat.string.clone()).collect()
17957}
17958
17959#[gpui::test]
17960async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
17961    init_test(cx, |settings| {
17962        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
17963            Formatter::Prettier,
17964        )))
17965    });
17966
17967    let fs = FakeFs::new(cx.executor());
17968    fs.insert_file(path!("/file.ts"), Default::default()).await;
17969
17970    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
17971    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17972
17973    language_registry.add(Arc::new(Language::new(
17974        LanguageConfig {
17975            name: "TypeScript".into(),
17976            matcher: LanguageMatcher {
17977                path_suffixes: vec!["ts".to_string()],
17978                ..Default::default()
17979            },
17980            ..Default::default()
17981        },
17982        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
17983    )));
17984    update_test_language_settings(cx, |settings| {
17985        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
17986    });
17987
17988    let test_plugin = "test_plugin";
17989    let _ = language_registry.register_fake_lsp(
17990        "TypeScript",
17991        FakeLspAdapter {
17992            prettier_plugins: vec![test_plugin],
17993            ..Default::default()
17994        },
17995    );
17996
17997    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
17998    let buffer = project
17999        .update(cx, |project, cx| {
18000            project.open_local_buffer(path!("/file.ts"), cx)
18001        })
18002        .await
18003        .unwrap();
18004
18005    let buffer_text = "one\ntwo\nthree\n";
18006    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18007    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18008    editor.update_in(cx, |editor, window, cx| {
18009        editor.set_text(buffer_text, window, cx)
18010    });
18011
18012    editor
18013        .update_in(cx, |editor, window, cx| {
18014            editor.perform_format(
18015                project.clone(),
18016                FormatTrigger::Manual,
18017                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18018                window,
18019                cx,
18020            )
18021        })
18022        .unwrap()
18023        .await;
18024    assert_eq!(
18025        editor.update(cx, |editor, cx| editor.text(cx)),
18026        buffer_text.to_string() + prettier_format_suffix,
18027        "Test prettier formatting was not applied to the original buffer text",
18028    );
18029
18030    update_test_language_settings(cx, |settings| {
18031        settings.defaults.formatter = Some(SelectedFormatter::Auto)
18032    });
18033    let format = editor.update_in(cx, |editor, window, cx| {
18034        editor.perform_format(
18035            project.clone(),
18036            FormatTrigger::Manual,
18037            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18038            window,
18039            cx,
18040        )
18041    });
18042    format.await.unwrap();
18043    assert_eq!(
18044        editor.update(cx, |editor, cx| editor.text(cx)),
18045        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18046        "Autoformatting (via test prettier) was not applied to the original buffer text",
18047    );
18048}
18049
18050#[gpui::test]
18051async fn test_addition_reverts(cx: &mut TestAppContext) {
18052    init_test(cx, |_| {});
18053    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18054    let base_text = indoc! {r#"
18055        struct Row;
18056        struct Row1;
18057        struct Row2;
18058
18059        struct Row4;
18060        struct Row5;
18061        struct Row6;
18062
18063        struct Row8;
18064        struct Row9;
18065        struct Row10;"#};
18066
18067    // When addition hunks are not adjacent to carets, no hunk revert is performed
18068    assert_hunk_revert(
18069        indoc! {r#"struct Row;
18070                   struct Row1;
18071                   struct Row1.1;
18072                   struct Row1.2;
18073                   struct Row2;ˇ
18074
18075                   struct Row4;
18076                   struct Row5;
18077                   struct Row6;
18078
18079                   struct Row8;
18080                   ˇstruct Row9;
18081                   struct Row9.1;
18082                   struct Row9.2;
18083                   struct Row9.3;
18084                   struct Row10;"#},
18085        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18086        indoc! {r#"struct Row;
18087                   struct Row1;
18088                   struct Row1.1;
18089                   struct Row1.2;
18090                   struct Row2;ˇ
18091
18092                   struct Row4;
18093                   struct Row5;
18094                   struct Row6;
18095
18096                   struct Row8;
18097                   ˇstruct Row9;
18098                   struct Row9.1;
18099                   struct Row9.2;
18100                   struct Row9.3;
18101                   struct Row10;"#},
18102        base_text,
18103        &mut cx,
18104    );
18105    // Same for selections
18106    assert_hunk_revert(
18107        indoc! {r#"struct Row;
18108                   struct Row1;
18109                   struct Row2;
18110                   struct Row2.1;
18111                   struct Row2.2;
18112                   «ˇ
18113                   struct Row4;
18114                   struct» Row5;
18115                   «struct Row6;
18116                   ˇ»
18117                   struct Row9.1;
18118                   struct Row9.2;
18119                   struct Row9.3;
18120                   struct Row8;
18121                   struct Row9;
18122                   struct Row10;"#},
18123        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18124        indoc! {r#"struct Row;
18125                   struct Row1;
18126                   struct Row2;
18127                   struct Row2.1;
18128                   struct Row2.2;
18129                   «ˇ
18130                   struct Row4;
18131                   struct» Row5;
18132                   «struct Row6;
18133                   ˇ»
18134                   struct Row9.1;
18135                   struct Row9.2;
18136                   struct Row9.3;
18137                   struct Row8;
18138                   struct Row9;
18139                   struct Row10;"#},
18140        base_text,
18141        &mut cx,
18142    );
18143
18144    // When carets and selections intersect the addition hunks, those are reverted.
18145    // Adjacent carets got merged.
18146    assert_hunk_revert(
18147        indoc! {r#"struct Row;
18148                   ˇ// something on the top
18149                   struct Row1;
18150                   struct Row2;
18151                   struct Roˇw3.1;
18152                   struct Row2.2;
18153                   struct Row2.3;ˇ
18154
18155                   struct Row4;
18156                   struct ˇRow5.1;
18157                   struct Row5.2;
18158                   struct «Rowˇ»5.3;
18159                   struct Row5;
18160                   struct Row6;
18161                   ˇ
18162                   struct Row9.1;
18163                   struct «Rowˇ»9.2;
18164                   struct «ˇRow»9.3;
18165                   struct Row8;
18166                   struct Row9;
18167                   «ˇ// something on bottom»
18168                   struct Row10;"#},
18169        vec![
18170            DiffHunkStatusKind::Added,
18171            DiffHunkStatusKind::Added,
18172            DiffHunkStatusKind::Added,
18173            DiffHunkStatusKind::Added,
18174            DiffHunkStatusKind::Added,
18175        ],
18176        indoc! {r#"struct Row;
18177                   ˇstruct Row1;
18178                   struct Row2;
18179                   ˇ
18180                   struct Row4;
18181                   ˇstruct Row5;
18182                   struct Row6;
18183                   ˇ
18184                   ˇstruct Row8;
18185                   struct Row9;
18186                   ˇstruct Row10;"#},
18187        base_text,
18188        &mut cx,
18189    );
18190}
18191
18192#[gpui::test]
18193async fn test_modification_reverts(cx: &mut TestAppContext) {
18194    init_test(cx, |_| {});
18195    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18196    let base_text = indoc! {r#"
18197        struct Row;
18198        struct Row1;
18199        struct Row2;
18200
18201        struct Row4;
18202        struct Row5;
18203        struct Row6;
18204
18205        struct Row8;
18206        struct Row9;
18207        struct Row10;"#};
18208
18209    // Modification hunks behave the same as the addition ones.
18210    assert_hunk_revert(
18211        indoc! {r#"struct Row;
18212                   struct Row1;
18213                   struct Row33;
18214                   ˇ
18215                   struct Row4;
18216                   struct Row5;
18217                   struct Row6;
18218                   ˇ
18219                   struct Row99;
18220                   struct Row9;
18221                   struct Row10;"#},
18222        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18223        indoc! {r#"struct Row;
18224                   struct Row1;
18225                   struct Row33;
18226                   ˇ
18227                   struct Row4;
18228                   struct Row5;
18229                   struct Row6;
18230                   ˇ
18231                   struct Row99;
18232                   struct Row9;
18233                   struct Row10;"#},
18234        base_text,
18235        &mut cx,
18236    );
18237    assert_hunk_revert(
18238        indoc! {r#"struct Row;
18239                   struct Row1;
18240                   struct Row33;
18241                   «ˇ
18242                   struct Row4;
18243                   struct» Row5;
18244                   «struct Row6;
18245                   ˇ»
18246                   struct Row99;
18247                   struct Row9;
18248                   struct Row10;"#},
18249        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18250        indoc! {r#"struct Row;
18251                   struct Row1;
18252                   struct Row33;
18253                   «ˇ
18254                   struct Row4;
18255                   struct» Row5;
18256                   «struct Row6;
18257                   ˇ»
18258                   struct Row99;
18259                   struct Row9;
18260                   struct Row10;"#},
18261        base_text,
18262        &mut cx,
18263    );
18264
18265    assert_hunk_revert(
18266        indoc! {r#"ˇstruct Row1.1;
18267                   struct Row1;
18268                   «ˇstr»uct Row22;
18269
18270                   struct ˇRow44;
18271                   struct Row5;
18272                   struct «Rˇ»ow66;ˇ
18273
18274                   «struˇ»ct Row88;
18275                   struct Row9;
18276                   struct Row1011;ˇ"#},
18277        vec![
18278            DiffHunkStatusKind::Modified,
18279            DiffHunkStatusKind::Modified,
18280            DiffHunkStatusKind::Modified,
18281            DiffHunkStatusKind::Modified,
18282            DiffHunkStatusKind::Modified,
18283            DiffHunkStatusKind::Modified,
18284        ],
18285        indoc! {r#"struct Row;
18286                   ˇstruct Row1;
18287                   struct Row2;
18288                   ˇ
18289                   struct Row4;
18290                   ˇstruct Row5;
18291                   struct Row6;
18292                   ˇ
18293                   struct Row8;
18294                   ˇstruct Row9;
18295                   struct Row10;ˇ"#},
18296        base_text,
18297        &mut cx,
18298    );
18299}
18300
18301#[gpui::test]
18302async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18303    init_test(cx, |_| {});
18304    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18305    let base_text = indoc! {r#"
18306        one
18307
18308        two
18309        three
18310        "#};
18311
18312    cx.set_head_text(base_text);
18313    cx.set_state("\nˇ\n");
18314    cx.executor().run_until_parked();
18315    cx.update_editor(|editor, _window, cx| {
18316        editor.expand_selected_diff_hunks(cx);
18317    });
18318    cx.executor().run_until_parked();
18319    cx.update_editor(|editor, window, cx| {
18320        editor.backspace(&Default::default(), window, cx);
18321    });
18322    cx.run_until_parked();
18323    cx.assert_state_with_diff(
18324        indoc! {r#"
18325
18326        - two
18327        - threeˇ
18328        +
18329        "#}
18330        .to_string(),
18331    );
18332}
18333
18334#[gpui::test]
18335async fn test_deletion_reverts(cx: &mut TestAppContext) {
18336    init_test(cx, |_| {});
18337    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18338    let base_text = indoc! {r#"struct Row;
18339struct Row1;
18340struct Row2;
18341
18342struct Row4;
18343struct Row5;
18344struct Row6;
18345
18346struct Row8;
18347struct Row9;
18348struct Row10;"#};
18349
18350    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18351    assert_hunk_revert(
18352        indoc! {r#"struct Row;
18353                   struct Row2;
18354
18355                   ˇstruct Row4;
18356                   struct Row5;
18357                   struct Row6;
18358                   ˇ
18359                   struct Row8;
18360                   struct Row10;"#},
18361        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18362        indoc! {r#"struct Row;
18363                   struct Row2;
18364
18365                   ˇstruct Row4;
18366                   struct Row5;
18367                   struct Row6;
18368                   ˇ
18369                   struct Row8;
18370                   struct Row10;"#},
18371        base_text,
18372        &mut cx,
18373    );
18374    assert_hunk_revert(
18375        indoc! {r#"struct Row;
18376                   struct Row2;
18377
18378                   «ˇstruct Row4;
18379                   struct» Row5;
18380                   «struct Row6;
18381                   ˇ»
18382                   struct Row8;
18383                   struct Row10;"#},
18384        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18385        indoc! {r#"struct Row;
18386                   struct Row2;
18387
18388                   «ˇstruct Row4;
18389                   struct» Row5;
18390                   «struct Row6;
18391                   ˇ»
18392                   struct Row8;
18393                   struct Row10;"#},
18394        base_text,
18395        &mut cx,
18396    );
18397
18398    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18399    assert_hunk_revert(
18400        indoc! {r#"struct Row;
18401                   ˇstruct Row2;
18402
18403                   struct Row4;
18404                   struct Row5;
18405                   struct Row6;
18406
18407                   struct Row8;ˇ
18408                   struct Row10;"#},
18409        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18410        indoc! {r#"struct Row;
18411                   struct Row1;
18412                   ˇstruct Row2;
18413
18414                   struct Row4;
18415                   struct Row5;
18416                   struct Row6;
18417
18418                   struct Row8;ˇ
18419                   struct Row9;
18420                   struct Row10;"#},
18421        base_text,
18422        &mut cx,
18423    );
18424    assert_hunk_revert(
18425        indoc! {r#"struct Row;
18426                   struct Row2«ˇ;
18427                   struct Row4;
18428                   struct» Row5;
18429                   «struct Row6;
18430
18431                   struct Row8;ˇ»
18432                   struct Row10;"#},
18433        vec![
18434            DiffHunkStatusKind::Deleted,
18435            DiffHunkStatusKind::Deleted,
18436            DiffHunkStatusKind::Deleted,
18437        ],
18438        indoc! {r#"struct Row;
18439                   struct Row1;
18440                   struct Row2«ˇ;
18441
18442                   struct Row4;
18443                   struct» Row5;
18444                   «struct Row6;
18445
18446                   struct Row8;ˇ»
18447                   struct Row9;
18448                   struct Row10;"#},
18449        base_text,
18450        &mut cx,
18451    );
18452}
18453
18454#[gpui::test]
18455async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18456    init_test(cx, |_| {});
18457
18458    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18459    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18460    let base_text_3 =
18461        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18462
18463    let text_1 = edit_first_char_of_every_line(base_text_1);
18464    let text_2 = edit_first_char_of_every_line(base_text_2);
18465    let text_3 = edit_first_char_of_every_line(base_text_3);
18466
18467    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18468    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18469    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18470
18471    let multibuffer = cx.new(|cx| {
18472        let mut multibuffer = MultiBuffer::new(ReadWrite);
18473        multibuffer.push_excerpts(
18474            buffer_1.clone(),
18475            [
18476                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18477                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18478                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18479            ],
18480            cx,
18481        );
18482        multibuffer.push_excerpts(
18483            buffer_2.clone(),
18484            [
18485                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18486                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18487                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18488            ],
18489            cx,
18490        );
18491        multibuffer.push_excerpts(
18492            buffer_3.clone(),
18493            [
18494                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18495                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18496                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18497            ],
18498            cx,
18499        );
18500        multibuffer
18501    });
18502
18503    let fs = FakeFs::new(cx.executor());
18504    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
18505    let (editor, cx) = cx
18506        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
18507    editor.update_in(cx, |editor, _window, cx| {
18508        for (buffer, diff_base) in [
18509            (buffer_1.clone(), base_text_1),
18510            (buffer_2.clone(), base_text_2),
18511            (buffer_3.clone(), base_text_3),
18512        ] {
18513            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18514            editor
18515                .buffer
18516                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18517        }
18518    });
18519    cx.executor().run_until_parked();
18520
18521    editor.update_in(cx, |editor, window, cx| {
18522        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}");
18523        editor.select_all(&SelectAll, window, cx);
18524        editor.git_restore(&Default::default(), window, cx);
18525    });
18526    cx.executor().run_until_parked();
18527
18528    // When all ranges are selected, all buffer hunks are reverted.
18529    editor.update(cx, |editor, cx| {
18530        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");
18531    });
18532    buffer_1.update(cx, |buffer, _| {
18533        assert_eq!(buffer.text(), base_text_1);
18534    });
18535    buffer_2.update(cx, |buffer, _| {
18536        assert_eq!(buffer.text(), base_text_2);
18537    });
18538    buffer_3.update(cx, |buffer, _| {
18539        assert_eq!(buffer.text(), base_text_3);
18540    });
18541
18542    editor.update_in(cx, |editor, window, cx| {
18543        editor.undo(&Default::default(), window, cx);
18544    });
18545
18546    editor.update_in(cx, |editor, window, cx| {
18547        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18548            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
18549        });
18550        editor.git_restore(&Default::default(), window, cx);
18551    });
18552
18553    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
18554    // but not affect buffer_2 and its related excerpts.
18555    editor.update(cx, |editor, cx| {
18556        assert_eq!(
18557            editor.text(cx),
18558            "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}"
18559        );
18560    });
18561    buffer_1.update(cx, |buffer, _| {
18562        assert_eq!(buffer.text(), base_text_1);
18563    });
18564    buffer_2.update(cx, |buffer, _| {
18565        assert_eq!(
18566            buffer.text(),
18567            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
18568        );
18569    });
18570    buffer_3.update(cx, |buffer, _| {
18571        assert_eq!(
18572            buffer.text(),
18573            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
18574        );
18575    });
18576
18577    fn edit_first_char_of_every_line(text: &str) -> String {
18578        text.split('\n')
18579            .map(|line| format!("X{}", &line[1..]))
18580            .collect::<Vec<_>>()
18581            .join("\n")
18582    }
18583}
18584
18585#[gpui::test]
18586async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
18587    init_test(cx, |_| {});
18588
18589    let cols = 4;
18590    let rows = 10;
18591    let sample_text_1 = sample_text(rows, cols, 'a');
18592    assert_eq!(
18593        sample_text_1,
18594        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
18595    );
18596    let sample_text_2 = sample_text(rows, cols, 'l');
18597    assert_eq!(
18598        sample_text_2,
18599        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
18600    );
18601    let sample_text_3 = sample_text(rows, cols, 'v');
18602    assert_eq!(
18603        sample_text_3,
18604        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
18605    );
18606
18607    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
18608    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
18609    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
18610
18611    let multi_buffer = cx.new(|cx| {
18612        let mut multibuffer = MultiBuffer::new(ReadWrite);
18613        multibuffer.push_excerpts(
18614            buffer_1.clone(),
18615            [
18616                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18617                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18618                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18619            ],
18620            cx,
18621        );
18622        multibuffer.push_excerpts(
18623            buffer_2.clone(),
18624            [
18625                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18626                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18627                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18628            ],
18629            cx,
18630        );
18631        multibuffer.push_excerpts(
18632            buffer_3.clone(),
18633            [
18634                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18635                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18636                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18637            ],
18638            cx,
18639        );
18640        multibuffer
18641    });
18642
18643    let fs = FakeFs::new(cx.executor());
18644    fs.insert_tree(
18645        "/a",
18646        json!({
18647            "main.rs": sample_text_1,
18648            "other.rs": sample_text_2,
18649            "lib.rs": sample_text_3,
18650        }),
18651    )
18652    .await;
18653    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18654    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18655    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18656    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18657        Editor::new(
18658            EditorMode::full(),
18659            multi_buffer,
18660            Some(project.clone()),
18661            window,
18662            cx,
18663        )
18664    });
18665    let multibuffer_item_id = workspace
18666        .update(cx, |workspace, window, cx| {
18667            assert!(
18668                workspace.active_item(cx).is_none(),
18669                "active item should be None before the first item is added"
18670            );
18671            workspace.add_item_to_active_pane(
18672                Box::new(multi_buffer_editor.clone()),
18673                None,
18674                true,
18675                window,
18676                cx,
18677            );
18678            let active_item = workspace
18679                .active_item(cx)
18680                .expect("should have an active item after adding the multi buffer");
18681            assert!(
18682                !active_item.is_singleton(cx),
18683                "A multi buffer was expected to active after adding"
18684            );
18685            active_item.item_id()
18686        })
18687        .unwrap();
18688    cx.executor().run_until_parked();
18689
18690    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18691        editor.change_selections(
18692            SelectionEffects::scroll(Autoscroll::Next),
18693            window,
18694            cx,
18695            |s| s.select_ranges(Some(1..2)),
18696        );
18697        editor.open_excerpts(&OpenExcerpts, window, cx);
18698    });
18699    cx.executor().run_until_parked();
18700    let first_item_id = workspace
18701        .update(cx, |workspace, window, cx| {
18702            let active_item = workspace
18703                .active_item(cx)
18704                .expect("should have an active item after navigating into the 1st buffer");
18705            let first_item_id = active_item.item_id();
18706            assert_ne!(
18707                first_item_id, multibuffer_item_id,
18708                "Should navigate into the 1st buffer and activate it"
18709            );
18710            assert!(
18711                active_item.is_singleton(cx),
18712                "New active item should be a singleton buffer"
18713            );
18714            assert_eq!(
18715                active_item
18716                    .act_as::<Editor>(cx)
18717                    .expect("should have navigated into an editor for the 1st buffer")
18718                    .read(cx)
18719                    .text(cx),
18720                sample_text_1
18721            );
18722
18723            workspace
18724                .go_back(workspace.active_pane().downgrade(), window, cx)
18725                .detach_and_log_err(cx);
18726
18727            first_item_id
18728        })
18729        .unwrap();
18730    cx.executor().run_until_parked();
18731    workspace
18732        .update(cx, |workspace, _, cx| {
18733            let active_item = workspace
18734                .active_item(cx)
18735                .expect("should have an active item after navigating back");
18736            assert_eq!(
18737                active_item.item_id(),
18738                multibuffer_item_id,
18739                "Should navigate back to the multi buffer"
18740            );
18741            assert!(!active_item.is_singleton(cx));
18742        })
18743        .unwrap();
18744
18745    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18746        editor.change_selections(
18747            SelectionEffects::scroll(Autoscroll::Next),
18748            window,
18749            cx,
18750            |s| s.select_ranges(Some(39..40)),
18751        );
18752        editor.open_excerpts(&OpenExcerpts, window, cx);
18753    });
18754    cx.executor().run_until_parked();
18755    let second_item_id = workspace
18756        .update(cx, |workspace, window, cx| {
18757            let active_item = workspace
18758                .active_item(cx)
18759                .expect("should have an active item after navigating into the 2nd buffer");
18760            let second_item_id = active_item.item_id();
18761            assert_ne!(
18762                second_item_id, multibuffer_item_id,
18763                "Should navigate away from the multibuffer"
18764            );
18765            assert_ne!(
18766                second_item_id, first_item_id,
18767                "Should navigate into the 2nd buffer and activate it"
18768            );
18769            assert!(
18770                active_item.is_singleton(cx),
18771                "New active item should be a singleton buffer"
18772            );
18773            assert_eq!(
18774                active_item
18775                    .act_as::<Editor>(cx)
18776                    .expect("should have navigated into an editor")
18777                    .read(cx)
18778                    .text(cx),
18779                sample_text_2
18780            );
18781
18782            workspace
18783                .go_back(workspace.active_pane().downgrade(), window, cx)
18784                .detach_and_log_err(cx);
18785
18786            second_item_id
18787        })
18788        .unwrap();
18789    cx.executor().run_until_parked();
18790    workspace
18791        .update(cx, |workspace, _, cx| {
18792            let active_item = workspace
18793                .active_item(cx)
18794                .expect("should have an active item after navigating back from the 2nd buffer");
18795            assert_eq!(
18796                active_item.item_id(),
18797                multibuffer_item_id,
18798                "Should navigate back from the 2nd buffer to the multi buffer"
18799            );
18800            assert!(!active_item.is_singleton(cx));
18801        })
18802        .unwrap();
18803
18804    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18805        editor.change_selections(
18806            SelectionEffects::scroll(Autoscroll::Next),
18807            window,
18808            cx,
18809            |s| s.select_ranges(Some(70..70)),
18810        );
18811        editor.open_excerpts(&OpenExcerpts, window, cx);
18812    });
18813    cx.executor().run_until_parked();
18814    workspace
18815        .update(cx, |workspace, window, cx| {
18816            let active_item = workspace
18817                .active_item(cx)
18818                .expect("should have an active item after navigating into the 3rd buffer");
18819            let third_item_id = active_item.item_id();
18820            assert_ne!(
18821                third_item_id, multibuffer_item_id,
18822                "Should navigate into the 3rd buffer and activate it"
18823            );
18824            assert_ne!(third_item_id, first_item_id);
18825            assert_ne!(third_item_id, second_item_id);
18826            assert!(
18827                active_item.is_singleton(cx),
18828                "New active item should be a singleton buffer"
18829            );
18830            assert_eq!(
18831                active_item
18832                    .act_as::<Editor>(cx)
18833                    .expect("should have navigated into an editor")
18834                    .read(cx)
18835                    .text(cx),
18836                sample_text_3
18837            );
18838
18839            workspace
18840                .go_back(workspace.active_pane().downgrade(), window, cx)
18841                .detach_and_log_err(cx);
18842        })
18843        .unwrap();
18844    cx.executor().run_until_parked();
18845    workspace
18846        .update(cx, |workspace, _, cx| {
18847            let active_item = workspace
18848                .active_item(cx)
18849                .expect("should have an active item after navigating back from the 3rd buffer");
18850            assert_eq!(
18851                active_item.item_id(),
18852                multibuffer_item_id,
18853                "Should navigate back from the 3rd buffer to the multi buffer"
18854            );
18855            assert!(!active_item.is_singleton(cx));
18856        })
18857        .unwrap();
18858}
18859
18860#[gpui::test]
18861async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18862    init_test(cx, |_| {});
18863
18864    let mut cx = EditorTestContext::new(cx).await;
18865
18866    let diff_base = r#"
18867        use some::mod;
18868
18869        const A: u32 = 42;
18870
18871        fn main() {
18872            println!("hello");
18873
18874            println!("world");
18875        }
18876        "#
18877    .unindent();
18878
18879    cx.set_state(
18880        &r#"
18881        use some::modified;
18882
18883        ˇ
18884        fn main() {
18885            println!("hello there");
18886
18887            println!("around the");
18888            println!("world");
18889        }
18890        "#
18891        .unindent(),
18892    );
18893
18894    cx.set_head_text(&diff_base);
18895    executor.run_until_parked();
18896
18897    cx.update_editor(|editor, window, cx| {
18898        editor.go_to_next_hunk(&GoToHunk, window, cx);
18899        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18900    });
18901    executor.run_until_parked();
18902    cx.assert_state_with_diff(
18903        r#"
18904          use some::modified;
18905
18906
18907          fn main() {
18908        -     println!("hello");
18909        + ˇ    println!("hello there");
18910
18911              println!("around the");
18912              println!("world");
18913          }
18914        "#
18915        .unindent(),
18916    );
18917
18918    cx.update_editor(|editor, window, cx| {
18919        for _ in 0..2 {
18920            editor.go_to_next_hunk(&GoToHunk, window, cx);
18921            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18922        }
18923    });
18924    executor.run_until_parked();
18925    cx.assert_state_with_diff(
18926        r#"
18927        - use some::mod;
18928        + ˇuse some::modified;
18929
18930
18931          fn main() {
18932        -     println!("hello");
18933        +     println!("hello there");
18934
18935        +     println!("around the");
18936              println!("world");
18937          }
18938        "#
18939        .unindent(),
18940    );
18941
18942    cx.update_editor(|editor, window, cx| {
18943        editor.go_to_next_hunk(&GoToHunk, window, cx);
18944        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18945    });
18946    executor.run_until_parked();
18947    cx.assert_state_with_diff(
18948        r#"
18949        - use some::mod;
18950        + use some::modified;
18951
18952        - const A: u32 = 42;
18953          ˇ
18954          fn main() {
18955        -     println!("hello");
18956        +     println!("hello there");
18957
18958        +     println!("around the");
18959              println!("world");
18960          }
18961        "#
18962        .unindent(),
18963    );
18964
18965    cx.update_editor(|editor, window, cx| {
18966        editor.cancel(&Cancel, window, cx);
18967    });
18968
18969    cx.assert_state_with_diff(
18970        r#"
18971          use some::modified;
18972
18973          ˇ
18974          fn main() {
18975              println!("hello there");
18976
18977              println!("around the");
18978              println!("world");
18979          }
18980        "#
18981        .unindent(),
18982    );
18983}
18984
18985#[gpui::test]
18986async fn test_diff_base_change_with_expanded_diff_hunks(
18987    executor: BackgroundExecutor,
18988    cx: &mut TestAppContext,
18989) {
18990    init_test(cx, |_| {});
18991
18992    let mut cx = EditorTestContext::new(cx).await;
18993
18994    let diff_base = r#"
18995        use some::mod1;
18996        use some::mod2;
18997
18998        const A: u32 = 42;
18999        const B: u32 = 42;
19000        const C: u32 = 42;
19001
19002        fn main() {
19003            println!("hello");
19004
19005            println!("world");
19006        }
19007        "#
19008    .unindent();
19009
19010    cx.set_state(
19011        &r#"
19012        use some::mod2;
19013
19014        const A: u32 = 42;
19015        const C: u32 = 42;
19016
19017        fn main(ˇ) {
19018            //println!("hello");
19019
19020            println!("world");
19021            //
19022            //
19023        }
19024        "#
19025        .unindent(),
19026    );
19027
19028    cx.set_head_text(&diff_base);
19029    executor.run_until_parked();
19030
19031    cx.update_editor(|editor, window, cx| {
19032        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19033    });
19034    executor.run_until_parked();
19035    cx.assert_state_with_diff(
19036        r#"
19037        - use some::mod1;
19038          use some::mod2;
19039
19040          const A: u32 = 42;
19041        - const B: u32 = 42;
19042          const C: u32 = 42;
19043
19044          fn main(ˇ) {
19045        -     println!("hello");
19046        +     //println!("hello");
19047
19048              println!("world");
19049        +     //
19050        +     //
19051          }
19052        "#
19053        .unindent(),
19054    );
19055
19056    cx.set_head_text("new diff base!");
19057    executor.run_until_parked();
19058    cx.assert_state_with_diff(
19059        r#"
19060        - new diff base!
19061        + use some::mod2;
19062        +
19063        + const A: u32 = 42;
19064        + const C: u32 = 42;
19065        +
19066        + fn main(ˇ) {
19067        +     //println!("hello");
19068        +
19069        +     println!("world");
19070        +     //
19071        +     //
19072        + }
19073        "#
19074        .unindent(),
19075    );
19076}
19077
19078#[gpui::test]
19079async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19080    init_test(cx, |_| {});
19081
19082    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19083    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19084    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19085    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19086    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19087    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19088
19089    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19090    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19091    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19092
19093    let multi_buffer = cx.new(|cx| {
19094        let mut multibuffer = MultiBuffer::new(ReadWrite);
19095        multibuffer.push_excerpts(
19096            buffer_1.clone(),
19097            [
19098                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19099                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19100                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19101            ],
19102            cx,
19103        );
19104        multibuffer.push_excerpts(
19105            buffer_2.clone(),
19106            [
19107                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19108                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19109                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19110            ],
19111            cx,
19112        );
19113        multibuffer.push_excerpts(
19114            buffer_3.clone(),
19115            [
19116                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19117                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19118                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19119            ],
19120            cx,
19121        );
19122        multibuffer
19123    });
19124
19125    let editor =
19126        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19127    editor
19128        .update(cx, |editor, _window, cx| {
19129            for (buffer, diff_base) in [
19130                (buffer_1.clone(), file_1_old),
19131                (buffer_2.clone(), file_2_old),
19132                (buffer_3.clone(), file_3_old),
19133            ] {
19134                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19135                editor
19136                    .buffer
19137                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19138            }
19139        })
19140        .unwrap();
19141
19142    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19143    cx.run_until_parked();
19144
19145    cx.assert_editor_state(
19146        &"
19147            ˇaaa
19148            ccc
19149            ddd
19150
19151            ggg
19152            hhh
19153
19154
19155            lll
19156            mmm
19157            NNN
19158
19159            qqq
19160            rrr
19161
19162            uuu
19163            111
19164            222
19165            333
19166
19167            666
19168            777
19169
19170            000
19171            !!!"
19172        .unindent(),
19173    );
19174
19175    cx.update_editor(|editor, window, cx| {
19176        editor.select_all(&SelectAll, window, cx);
19177        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19178    });
19179    cx.executor().run_until_parked();
19180
19181    cx.assert_state_with_diff(
19182        "
19183            «aaa
19184          - bbb
19185            ccc
19186            ddd
19187
19188            ggg
19189            hhh
19190
19191
19192            lll
19193            mmm
19194          - nnn
19195          + NNN
19196
19197            qqq
19198            rrr
19199
19200            uuu
19201            111
19202            222
19203            333
19204
19205          + 666
19206            777
19207
19208            000
19209            !!!ˇ»"
19210            .unindent(),
19211    );
19212}
19213
19214#[gpui::test]
19215async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19216    init_test(cx, |_| {});
19217
19218    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19219    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19220
19221    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19222    let multi_buffer = cx.new(|cx| {
19223        let mut multibuffer = MultiBuffer::new(ReadWrite);
19224        multibuffer.push_excerpts(
19225            buffer.clone(),
19226            [
19227                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19228                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19229                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19230            ],
19231            cx,
19232        );
19233        multibuffer
19234    });
19235
19236    let editor =
19237        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19238    editor
19239        .update(cx, |editor, _window, cx| {
19240            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19241            editor
19242                .buffer
19243                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19244        })
19245        .unwrap();
19246
19247    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19248    cx.run_until_parked();
19249
19250    cx.update_editor(|editor, window, cx| {
19251        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19252    });
19253    cx.executor().run_until_parked();
19254
19255    // When the start of a hunk coincides with the start of its excerpt,
19256    // the hunk is expanded. When the start of a hunk is earlier than
19257    // the start of its excerpt, the hunk is not expanded.
19258    cx.assert_state_with_diff(
19259        "
19260            ˇaaa
19261          - bbb
19262          + BBB
19263
19264          - ddd
19265          - eee
19266          + DDD
19267          + EEE
19268            fff
19269
19270            iii
19271        "
19272        .unindent(),
19273    );
19274}
19275
19276#[gpui::test]
19277async fn test_edits_around_expanded_insertion_hunks(
19278    executor: BackgroundExecutor,
19279    cx: &mut TestAppContext,
19280) {
19281    init_test(cx, |_| {});
19282
19283    let mut cx = EditorTestContext::new(cx).await;
19284
19285    let diff_base = r#"
19286        use some::mod1;
19287        use some::mod2;
19288
19289        const A: u32 = 42;
19290
19291        fn main() {
19292            println!("hello");
19293
19294            println!("world");
19295        }
19296        "#
19297    .unindent();
19298    executor.run_until_parked();
19299    cx.set_state(
19300        &r#"
19301        use some::mod1;
19302        use some::mod2;
19303
19304        const A: u32 = 42;
19305        const B: u32 = 42;
19306        const C: u32 = 42;
19307        ˇ
19308
19309        fn main() {
19310            println!("hello");
19311
19312            println!("world");
19313        }
19314        "#
19315        .unindent(),
19316    );
19317
19318    cx.set_head_text(&diff_base);
19319    executor.run_until_parked();
19320
19321    cx.update_editor(|editor, window, cx| {
19322        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19323    });
19324    executor.run_until_parked();
19325
19326    cx.assert_state_with_diff(
19327        r#"
19328        use some::mod1;
19329        use some::mod2;
19330
19331        const A: u32 = 42;
19332      + const B: u32 = 42;
19333      + const C: u32 = 42;
19334      + ˇ
19335
19336        fn main() {
19337            println!("hello");
19338
19339            println!("world");
19340        }
19341      "#
19342        .unindent(),
19343    );
19344
19345    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19346    executor.run_until_parked();
19347
19348    cx.assert_state_with_diff(
19349        r#"
19350        use some::mod1;
19351        use some::mod2;
19352
19353        const A: u32 = 42;
19354      + const B: u32 = 42;
19355      + const C: u32 = 42;
19356      + const D: u32 = 42;
19357      + ˇ
19358
19359        fn main() {
19360            println!("hello");
19361
19362            println!("world");
19363        }
19364      "#
19365        .unindent(),
19366    );
19367
19368    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19369    executor.run_until_parked();
19370
19371    cx.assert_state_with_diff(
19372        r#"
19373        use some::mod1;
19374        use some::mod2;
19375
19376        const A: u32 = 42;
19377      + const B: u32 = 42;
19378      + const C: u32 = 42;
19379      + const D: u32 = 42;
19380      + const E: u32 = 42;
19381      + ˇ
19382
19383        fn main() {
19384            println!("hello");
19385
19386            println!("world");
19387        }
19388      "#
19389        .unindent(),
19390    );
19391
19392    cx.update_editor(|editor, window, cx| {
19393        editor.delete_line(&DeleteLine, window, cx);
19394    });
19395    executor.run_until_parked();
19396
19397    cx.assert_state_with_diff(
19398        r#"
19399        use some::mod1;
19400        use some::mod2;
19401
19402        const A: u32 = 42;
19403      + const B: u32 = 42;
19404      + const C: u32 = 42;
19405      + const D: u32 = 42;
19406      + const E: u32 = 42;
19407        ˇ
19408        fn main() {
19409            println!("hello");
19410
19411            println!("world");
19412        }
19413      "#
19414        .unindent(),
19415    );
19416
19417    cx.update_editor(|editor, window, cx| {
19418        editor.move_up(&MoveUp, window, cx);
19419        editor.delete_line(&DeleteLine, window, cx);
19420        editor.move_up(&MoveUp, window, cx);
19421        editor.delete_line(&DeleteLine, window, cx);
19422        editor.move_up(&MoveUp, window, cx);
19423        editor.delete_line(&DeleteLine, window, cx);
19424    });
19425    executor.run_until_parked();
19426    cx.assert_state_with_diff(
19427        r#"
19428        use some::mod1;
19429        use some::mod2;
19430
19431        const A: u32 = 42;
19432      + const B: u32 = 42;
19433        ˇ
19434        fn main() {
19435            println!("hello");
19436
19437            println!("world");
19438        }
19439      "#
19440        .unindent(),
19441    );
19442
19443    cx.update_editor(|editor, window, cx| {
19444        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19445        editor.delete_line(&DeleteLine, window, cx);
19446    });
19447    executor.run_until_parked();
19448    cx.assert_state_with_diff(
19449        r#"
19450        ˇ
19451        fn main() {
19452            println!("hello");
19453
19454            println!("world");
19455        }
19456      "#
19457        .unindent(),
19458    );
19459}
19460
19461#[gpui::test]
19462async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19463    init_test(cx, |_| {});
19464
19465    let mut cx = EditorTestContext::new(cx).await;
19466    cx.set_head_text(indoc! { "
19467        one
19468        two
19469        three
19470        four
19471        five
19472        "
19473    });
19474    cx.set_state(indoc! { "
19475        one
19476        ˇthree
19477        five
19478    "});
19479    cx.run_until_parked();
19480    cx.update_editor(|editor, window, cx| {
19481        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19482    });
19483    cx.assert_state_with_diff(
19484        indoc! { "
19485        one
19486      - two
19487        ˇthree
19488      - four
19489        five
19490    "}
19491        .to_string(),
19492    );
19493    cx.update_editor(|editor, window, cx| {
19494        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19495    });
19496
19497    cx.assert_state_with_diff(
19498        indoc! { "
19499        one
19500        ˇthree
19501        five
19502    "}
19503        .to_string(),
19504    );
19505
19506    cx.set_state(indoc! { "
19507        one
19508        ˇTWO
19509        three
19510        four
19511        five
19512    "});
19513    cx.run_until_parked();
19514    cx.update_editor(|editor, window, cx| {
19515        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19516    });
19517
19518    cx.assert_state_with_diff(
19519        indoc! { "
19520            one
19521          - two
19522          + ˇTWO
19523            three
19524            four
19525            five
19526        "}
19527        .to_string(),
19528    );
19529    cx.update_editor(|editor, window, cx| {
19530        editor.move_up(&Default::default(), window, cx);
19531        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19532    });
19533    cx.assert_state_with_diff(
19534        indoc! { "
19535            one
19536            ˇTWO
19537            three
19538            four
19539            five
19540        "}
19541        .to_string(),
19542    );
19543}
19544
19545#[gpui::test]
19546async fn test_edits_around_expanded_deletion_hunks(
19547    executor: BackgroundExecutor,
19548    cx: &mut TestAppContext,
19549) {
19550    init_test(cx, |_| {});
19551
19552    let mut cx = EditorTestContext::new(cx).await;
19553
19554    let diff_base = r#"
19555        use some::mod1;
19556        use some::mod2;
19557
19558        const A: u32 = 42;
19559        const B: u32 = 42;
19560        const C: u32 = 42;
19561
19562
19563        fn main() {
19564            println!("hello");
19565
19566            println!("world");
19567        }
19568    "#
19569    .unindent();
19570    executor.run_until_parked();
19571    cx.set_state(
19572        &r#"
19573        use some::mod1;
19574        use some::mod2;
19575
19576        ˇconst B: u32 = 42;
19577        const C: u32 = 42;
19578
19579
19580        fn main() {
19581            println!("hello");
19582
19583            println!("world");
19584        }
19585        "#
19586        .unindent(),
19587    );
19588
19589    cx.set_head_text(&diff_base);
19590    executor.run_until_parked();
19591
19592    cx.update_editor(|editor, window, cx| {
19593        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19594    });
19595    executor.run_until_parked();
19596
19597    cx.assert_state_with_diff(
19598        r#"
19599        use some::mod1;
19600        use some::mod2;
19601
19602      - const A: u32 = 42;
19603        ˇconst B: u32 = 42;
19604        const C: u32 = 42;
19605
19606
19607        fn main() {
19608            println!("hello");
19609
19610            println!("world");
19611        }
19612      "#
19613        .unindent(),
19614    );
19615
19616    cx.update_editor(|editor, window, cx| {
19617        editor.delete_line(&DeleteLine, window, cx);
19618    });
19619    executor.run_until_parked();
19620    cx.assert_state_with_diff(
19621        r#"
19622        use some::mod1;
19623        use some::mod2;
19624
19625      - const A: u32 = 42;
19626      - const B: u32 = 42;
19627        ˇconst C: u32 = 42;
19628
19629
19630        fn main() {
19631            println!("hello");
19632
19633            println!("world");
19634        }
19635      "#
19636        .unindent(),
19637    );
19638
19639    cx.update_editor(|editor, window, cx| {
19640        editor.delete_line(&DeleteLine, window, cx);
19641    });
19642    executor.run_until_parked();
19643    cx.assert_state_with_diff(
19644        r#"
19645        use some::mod1;
19646        use some::mod2;
19647
19648      - const A: u32 = 42;
19649      - const B: u32 = 42;
19650      - const C: u32 = 42;
19651        ˇ
19652
19653        fn main() {
19654            println!("hello");
19655
19656            println!("world");
19657        }
19658      "#
19659        .unindent(),
19660    );
19661
19662    cx.update_editor(|editor, window, cx| {
19663        editor.handle_input("replacement", window, cx);
19664    });
19665    executor.run_until_parked();
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      + replacementˇ
19676
19677        fn main() {
19678            println!("hello");
19679
19680            println!("world");
19681        }
19682      "#
19683        .unindent(),
19684    );
19685}
19686
19687#[gpui::test]
19688async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19689    init_test(cx, |_| {});
19690
19691    let mut cx = EditorTestContext::new(cx).await;
19692
19693    let base_text = r#"
19694        one
19695        two
19696        three
19697        four
19698        five
19699    "#
19700    .unindent();
19701    executor.run_until_parked();
19702    cx.set_state(
19703        &r#"
19704        one
19705        two
19706        fˇour
19707        five
19708        "#
19709        .unindent(),
19710    );
19711
19712    cx.set_head_text(&base_text);
19713    executor.run_until_parked();
19714
19715    cx.update_editor(|editor, window, cx| {
19716        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19717    });
19718    executor.run_until_parked();
19719
19720    cx.assert_state_with_diff(
19721        r#"
19722          one
19723          two
19724        - three
19725          fˇour
19726          five
19727        "#
19728        .unindent(),
19729    );
19730
19731    cx.update_editor(|editor, window, cx| {
19732        editor.backspace(&Backspace, window, cx);
19733        editor.backspace(&Backspace, window, cx);
19734    });
19735    executor.run_until_parked();
19736    cx.assert_state_with_diff(
19737        r#"
19738          one
19739          two
19740        - threeˇ
19741        - four
19742        + our
19743          five
19744        "#
19745        .unindent(),
19746    );
19747}
19748
19749#[gpui::test]
19750async fn test_edit_after_expanded_modification_hunk(
19751    executor: BackgroundExecutor,
19752    cx: &mut TestAppContext,
19753) {
19754    init_test(cx, |_| {});
19755
19756    let mut cx = EditorTestContext::new(cx).await;
19757
19758    let diff_base = r#"
19759        use some::mod1;
19760        use some::mod2;
19761
19762        const A: u32 = 42;
19763        const B: u32 = 42;
19764        const C: u32 = 42;
19765        const D: u32 = 42;
19766
19767
19768        fn main() {
19769            println!("hello");
19770
19771            println!("world");
19772        }"#
19773    .unindent();
19774
19775    cx.set_state(
19776        &r#"
19777        use some::mod1;
19778        use some::mod2;
19779
19780        const A: u32 = 42;
19781        const B: u32 = 42;
19782        const C: u32 = 43ˇ
19783        const D: u32 = 42;
19784
19785
19786        fn main() {
19787            println!("hello");
19788
19789            println!("world");
19790        }"#
19791        .unindent(),
19792    );
19793
19794    cx.set_head_text(&diff_base);
19795    executor.run_until_parked();
19796    cx.update_editor(|editor, window, cx| {
19797        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19798    });
19799    executor.run_until_parked();
19800
19801    cx.assert_state_with_diff(
19802        r#"
19803        use some::mod1;
19804        use some::mod2;
19805
19806        const A: u32 = 42;
19807        const B: u32 = 42;
19808      - const C: u32 = 42;
19809      + const C: u32 = 43ˇ
19810        const D: u32 = 42;
19811
19812
19813        fn main() {
19814            println!("hello");
19815
19816            println!("world");
19817        }"#
19818        .unindent(),
19819    );
19820
19821    cx.update_editor(|editor, window, cx| {
19822        editor.handle_input("\nnew_line\n", window, cx);
19823    });
19824    executor.run_until_parked();
19825
19826    cx.assert_state_with_diff(
19827        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 C: u32 = 43
19835      + new_line
19836      + ˇ
19837        const D: u32 = 42;
19838
19839
19840        fn main() {
19841            println!("hello");
19842
19843            println!("world");
19844        }"#
19845        .unindent(),
19846    );
19847}
19848
19849#[gpui::test]
19850async fn test_stage_and_unstage_added_file_hunk(
19851    executor: BackgroundExecutor,
19852    cx: &mut TestAppContext,
19853) {
19854    init_test(cx, |_| {});
19855
19856    let mut cx = EditorTestContext::new(cx).await;
19857    cx.update_editor(|editor, _, cx| {
19858        editor.set_expand_all_diff_hunks(cx);
19859    });
19860
19861    let working_copy = r#"
19862            ˇfn main() {
19863                println!("hello, world!");
19864            }
19865        "#
19866    .unindent();
19867
19868    cx.set_state(&working_copy);
19869    executor.run_until_parked();
19870
19871    cx.assert_state_with_diff(
19872        r#"
19873            + ˇfn main() {
19874            +     println!("hello, world!");
19875            + }
19876        "#
19877        .unindent(),
19878    );
19879    cx.assert_index_text(None);
19880
19881    cx.update_editor(|editor, window, cx| {
19882        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19883    });
19884    executor.run_until_parked();
19885    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
19886    cx.assert_state_with_diff(
19887        r#"
19888            + ˇfn main() {
19889            +     println!("hello, world!");
19890            + }
19891        "#
19892        .unindent(),
19893    );
19894
19895    cx.update_editor(|editor, window, cx| {
19896        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19897    });
19898    executor.run_until_parked();
19899    cx.assert_index_text(None);
19900}
19901
19902async fn setup_indent_guides_editor(
19903    text: &str,
19904    cx: &mut TestAppContext,
19905) -> (BufferId, EditorTestContext) {
19906    init_test(cx, |_| {});
19907
19908    let mut cx = EditorTestContext::new(cx).await;
19909
19910    let buffer_id = cx.update_editor(|editor, window, cx| {
19911        editor.set_text(text, window, cx);
19912        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
19913
19914        buffer_ids[0]
19915    });
19916
19917    (buffer_id, cx)
19918}
19919
19920fn assert_indent_guides(
19921    range: Range<u32>,
19922    expected: Vec<IndentGuide>,
19923    active_indices: Option<Vec<usize>>,
19924    cx: &mut EditorTestContext,
19925) {
19926    let indent_guides = cx.update_editor(|editor, window, cx| {
19927        let snapshot = editor.snapshot(window, cx).display_snapshot;
19928        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
19929            editor,
19930            MultiBufferRow(range.start)..MultiBufferRow(range.end),
19931            true,
19932            &snapshot,
19933            cx,
19934        );
19935
19936        indent_guides.sort_by(|a, b| {
19937            a.depth.cmp(&b.depth).then(
19938                a.start_row
19939                    .cmp(&b.start_row)
19940                    .then(a.end_row.cmp(&b.end_row)),
19941            )
19942        });
19943        indent_guides
19944    });
19945
19946    if let Some(expected) = active_indices {
19947        let active_indices = cx.update_editor(|editor, window, cx| {
19948            let snapshot = editor.snapshot(window, cx).display_snapshot;
19949            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
19950        });
19951
19952        assert_eq!(
19953            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
19954            expected,
19955            "Active indent guide indices do not match"
19956        );
19957    }
19958
19959    assert_eq!(indent_guides, expected, "Indent guides do not match");
19960}
19961
19962fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
19963    IndentGuide {
19964        buffer_id,
19965        start_row: MultiBufferRow(start_row),
19966        end_row: MultiBufferRow(end_row),
19967        depth,
19968        tab_size: 4,
19969        settings: IndentGuideSettings {
19970            enabled: true,
19971            line_width: 1,
19972            active_line_width: 1,
19973            coloring: IndentGuideColoring::default(),
19974            background_coloring: IndentGuideBackgroundColoring::default(),
19975        },
19976    }
19977}
19978
19979#[gpui::test]
19980async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
19981    let (buffer_id, mut cx) = setup_indent_guides_editor(
19982        &"
19983        fn main() {
19984            let a = 1;
19985        }"
19986        .unindent(),
19987        cx,
19988    )
19989    .await;
19990
19991    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
19992}
19993
19994#[gpui::test]
19995async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
19996    let (buffer_id, mut cx) = setup_indent_guides_editor(
19997        &"
19998        fn main() {
19999            let a = 1;
20000            let b = 2;
20001        }"
20002        .unindent(),
20003        cx,
20004    )
20005    .await;
20006
20007    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20008}
20009
20010#[gpui::test]
20011async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20012    let (buffer_id, mut cx) = setup_indent_guides_editor(
20013        &"
20014        fn main() {
20015            let a = 1;
20016            if a == 3 {
20017                let b = 2;
20018            } else {
20019                let c = 3;
20020            }
20021        }"
20022        .unindent(),
20023        cx,
20024    )
20025    .await;
20026
20027    assert_indent_guides(
20028        0..8,
20029        vec![
20030            indent_guide(buffer_id, 1, 6, 0),
20031            indent_guide(buffer_id, 3, 3, 1),
20032            indent_guide(buffer_id, 5, 5, 1),
20033        ],
20034        None,
20035        &mut cx,
20036    );
20037}
20038
20039#[gpui::test]
20040async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20041    let (buffer_id, mut cx) = setup_indent_guides_editor(
20042        &"
20043        fn main() {
20044            let a = 1;
20045                let b = 2;
20046            let c = 3;
20047        }"
20048        .unindent(),
20049        cx,
20050    )
20051    .await;
20052
20053    assert_indent_guides(
20054        0..5,
20055        vec![
20056            indent_guide(buffer_id, 1, 3, 0),
20057            indent_guide(buffer_id, 2, 2, 1),
20058        ],
20059        None,
20060        &mut cx,
20061    );
20062}
20063
20064#[gpui::test]
20065async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20066    let (buffer_id, mut cx) = setup_indent_guides_editor(
20067        &"
20068        fn main() {
20069            let a = 1;
20070
20071            let c = 3;
20072        }"
20073        .unindent(),
20074        cx,
20075    )
20076    .await;
20077
20078    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20079}
20080
20081#[gpui::test]
20082async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20083    let (buffer_id, mut cx) = setup_indent_guides_editor(
20084        &"
20085        fn main() {
20086            let a = 1;
20087
20088            let c = 3;
20089
20090            if a == 3 {
20091                let b = 2;
20092            } else {
20093                let c = 3;
20094            }
20095        }"
20096        .unindent(),
20097        cx,
20098    )
20099    .await;
20100
20101    assert_indent_guides(
20102        0..11,
20103        vec![
20104            indent_guide(buffer_id, 1, 9, 0),
20105            indent_guide(buffer_id, 6, 6, 1),
20106            indent_guide(buffer_id, 8, 8, 1),
20107        ],
20108        None,
20109        &mut cx,
20110    );
20111}
20112
20113#[gpui::test]
20114async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20115    let (buffer_id, mut cx) = setup_indent_guides_editor(
20116        &"
20117        fn main() {
20118            let a = 1;
20119
20120            let c = 3;
20121
20122            if a == 3 {
20123                let b = 2;
20124            } else {
20125                let c = 3;
20126            }
20127        }"
20128        .unindent(),
20129        cx,
20130    )
20131    .await;
20132
20133    assert_indent_guides(
20134        1..11,
20135        vec![
20136            indent_guide(buffer_id, 1, 9, 0),
20137            indent_guide(buffer_id, 6, 6, 1),
20138            indent_guide(buffer_id, 8, 8, 1),
20139        ],
20140        None,
20141        &mut cx,
20142    );
20143}
20144
20145#[gpui::test]
20146async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20147    let (buffer_id, mut cx) = setup_indent_guides_editor(
20148        &"
20149        fn main() {
20150            let a = 1;
20151
20152            let c = 3;
20153
20154            if a == 3 {
20155                let b = 2;
20156            } else {
20157                let c = 3;
20158            }
20159        }"
20160        .unindent(),
20161        cx,
20162    )
20163    .await;
20164
20165    assert_indent_guides(
20166        1..10,
20167        vec![
20168            indent_guide(buffer_id, 1, 9, 0),
20169            indent_guide(buffer_id, 6, 6, 1),
20170            indent_guide(buffer_id, 8, 8, 1),
20171        ],
20172        None,
20173        &mut cx,
20174    );
20175}
20176
20177#[gpui::test]
20178async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20179    let (buffer_id, mut cx) = setup_indent_guides_editor(
20180        &"
20181        fn main() {
20182            if a {
20183                b(
20184                    c,
20185                    d,
20186                )
20187            } else {
20188                e(
20189                    f
20190                )
20191            }
20192        }"
20193        .unindent(),
20194        cx,
20195    )
20196    .await;
20197
20198    assert_indent_guides(
20199        0..11,
20200        vec![
20201            indent_guide(buffer_id, 1, 10, 0),
20202            indent_guide(buffer_id, 2, 5, 1),
20203            indent_guide(buffer_id, 7, 9, 1),
20204            indent_guide(buffer_id, 3, 4, 2),
20205            indent_guide(buffer_id, 8, 8, 2),
20206        ],
20207        None,
20208        &mut cx,
20209    );
20210
20211    cx.update_editor(|editor, window, cx| {
20212        editor.fold_at(MultiBufferRow(2), window, cx);
20213        assert_eq!(
20214            editor.display_text(cx),
20215            "
20216            fn main() {
20217                if a {
20218                    b(⋯
20219                    )
20220                } else {
20221                    e(
20222                        f
20223                    )
20224                }
20225            }"
20226            .unindent()
20227        );
20228    });
20229
20230    assert_indent_guides(
20231        0..11,
20232        vec![
20233            indent_guide(buffer_id, 1, 10, 0),
20234            indent_guide(buffer_id, 2, 5, 1),
20235            indent_guide(buffer_id, 7, 9, 1),
20236            indent_guide(buffer_id, 8, 8, 2),
20237        ],
20238        None,
20239        &mut cx,
20240    );
20241}
20242
20243#[gpui::test]
20244async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20245    let (buffer_id, mut cx) = setup_indent_guides_editor(
20246        &"
20247        block1
20248            block2
20249                block3
20250                    block4
20251            block2
20252        block1
20253        block1"
20254            .unindent(),
20255        cx,
20256    )
20257    .await;
20258
20259    assert_indent_guides(
20260        1..10,
20261        vec![
20262            indent_guide(buffer_id, 1, 4, 0),
20263            indent_guide(buffer_id, 2, 3, 1),
20264            indent_guide(buffer_id, 3, 3, 2),
20265        ],
20266        None,
20267        &mut cx,
20268    );
20269}
20270
20271#[gpui::test]
20272async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20273    let (buffer_id, mut cx) = setup_indent_guides_editor(
20274        &"
20275        block1
20276            block2
20277                block3
20278
20279        block1
20280        block1"
20281            .unindent(),
20282        cx,
20283    )
20284    .await;
20285
20286    assert_indent_guides(
20287        0..6,
20288        vec![
20289            indent_guide(buffer_id, 1, 2, 0),
20290            indent_guide(buffer_id, 2, 2, 1),
20291        ],
20292        None,
20293        &mut cx,
20294    );
20295}
20296
20297#[gpui::test]
20298async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20299    let (buffer_id, mut cx) = setup_indent_guides_editor(
20300        &"
20301        function component() {
20302        \treturn (
20303        \t\t\t
20304        \t\t<div>
20305        \t\t\t<abc></abc>
20306        \t\t</div>
20307        \t)
20308        }"
20309        .unindent(),
20310        cx,
20311    )
20312    .await;
20313
20314    assert_indent_guides(
20315        0..8,
20316        vec![
20317            indent_guide(buffer_id, 1, 6, 0),
20318            indent_guide(buffer_id, 2, 5, 1),
20319            indent_guide(buffer_id, 4, 4, 2),
20320        ],
20321        None,
20322        &mut cx,
20323    );
20324}
20325
20326#[gpui::test]
20327async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20328    let (buffer_id, mut cx) = setup_indent_guides_editor(
20329        &"
20330        function component() {
20331        \treturn (
20332        \t
20333        \t\t<div>
20334        \t\t\t<abc></abc>
20335        \t\t</div>
20336        \t)
20337        }"
20338        .unindent(),
20339        cx,
20340    )
20341    .await;
20342
20343    assert_indent_guides(
20344        0..8,
20345        vec![
20346            indent_guide(buffer_id, 1, 6, 0),
20347            indent_guide(buffer_id, 2, 5, 1),
20348            indent_guide(buffer_id, 4, 4, 2),
20349        ],
20350        None,
20351        &mut cx,
20352    );
20353}
20354
20355#[gpui::test]
20356async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20357    let (buffer_id, mut cx) = setup_indent_guides_editor(
20358        &"
20359        block1
20360
20361
20362
20363            block2
20364        "
20365        .unindent(),
20366        cx,
20367    )
20368    .await;
20369
20370    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20371}
20372
20373#[gpui::test]
20374async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20375    let (buffer_id, mut cx) = setup_indent_guides_editor(
20376        &"
20377        def a:
20378        \tb = 3
20379        \tif True:
20380        \t\tc = 4
20381        \t\td = 5
20382        \tprint(b)
20383        "
20384        .unindent(),
20385        cx,
20386    )
20387    .await;
20388
20389    assert_indent_guides(
20390        0..6,
20391        vec![
20392            indent_guide(buffer_id, 1, 5, 0),
20393            indent_guide(buffer_id, 3, 4, 1),
20394        ],
20395        None,
20396        &mut cx,
20397    );
20398}
20399
20400#[gpui::test]
20401async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20402    let (buffer_id, mut cx) = setup_indent_guides_editor(
20403        &"
20404    fn main() {
20405        let a = 1;
20406    }"
20407        .unindent(),
20408        cx,
20409    )
20410    .await;
20411
20412    cx.update_editor(|editor, window, cx| {
20413        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20414            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20415        });
20416    });
20417
20418    assert_indent_guides(
20419        0..3,
20420        vec![indent_guide(buffer_id, 1, 1, 0)],
20421        Some(vec![0]),
20422        &mut cx,
20423    );
20424}
20425
20426#[gpui::test]
20427async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20428    let (buffer_id, mut cx) = setup_indent_guides_editor(
20429        &"
20430    fn main() {
20431        if 1 == 2 {
20432            let a = 1;
20433        }
20434    }"
20435        .unindent(),
20436        cx,
20437    )
20438    .await;
20439
20440    cx.update_editor(|editor, window, cx| {
20441        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20442            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20443        });
20444    });
20445
20446    assert_indent_guides(
20447        0..4,
20448        vec![
20449            indent_guide(buffer_id, 1, 3, 0),
20450            indent_guide(buffer_id, 2, 2, 1),
20451        ],
20452        Some(vec![1]),
20453        &mut cx,
20454    );
20455
20456    cx.update_editor(|editor, window, cx| {
20457        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20458            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20459        });
20460    });
20461
20462    assert_indent_guides(
20463        0..4,
20464        vec![
20465            indent_guide(buffer_id, 1, 3, 0),
20466            indent_guide(buffer_id, 2, 2, 1),
20467        ],
20468        Some(vec![1]),
20469        &mut cx,
20470    );
20471
20472    cx.update_editor(|editor, window, cx| {
20473        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20474            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20475        });
20476    });
20477
20478    assert_indent_guides(
20479        0..4,
20480        vec![
20481            indent_guide(buffer_id, 1, 3, 0),
20482            indent_guide(buffer_id, 2, 2, 1),
20483        ],
20484        Some(vec![0]),
20485        &mut cx,
20486    );
20487}
20488
20489#[gpui::test]
20490async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
20491    let (buffer_id, mut cx) = setup_indent_guides_editor(
20492        &"
20493    fn main() {
20494        let a = 1;
20495
20496        let b = 2;
20497    }"
20498        .unindent(),
20499        cx,
20500    )
20501    .await;
20502
20503    cx.update_editor(|editor, window, cx| {
20504        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20505            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20506        });
20507    });
20508
20509    assert_indent_guides(
20510        0..5,
20511        vec![indent_guide(buffer_id, 1, 3, 0)],
20512        Some(vec![0]),
20513        &mut cx,
20514    );
20515}
20516
20517#[gpui::test]
20518async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
20519    let (buffer_id, mut cx) = setup_indent_guides_editor(
20520        &"
20521    def m:
20522        a = 1
20523        pass"
20524            .unindent(),
20525        cx,
20526    )
20527    .await;
20528
20529    cx.update_editor(|editor, window, cx| {
20530        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20531            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20532        });
20533    });
20534
20535    assert_indent_guides(
20536        0..3,
20537        vec![indent_guide(buffer_id, 1, 2, 0)],
20538        Some(vec![0]),
20539        &mut cx,
20540    );
20541}
20542
20543#[gpui::test]
20544async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
20545    init_test(cx, |_| {});
20546    let mut cx = EditorTestContext::new(cx).await;
20547    let text = indoc! {
20548        "
20549        impl A {
20550            fn b() {
20551                0;
20552                3;
20553                5;
20554                6;
20555                7;
20556            }
20557        }
20558        "
20559    };
20560    let base_text = indoc! {
20561        "
20562        impl A {
20563            fn b() {
20564                0;
20565                1;
20566                2;
20567                3;
20568                4;
20569            }
20570            fn c() {
20571                5;
20572                6;
20573                7;
20574            }
20575        }
20576        "
20577    };
20578
20579    cx.update_editor(|editor, window, cx| {
20580        editor.set_text(text, window, cx);
20581
20582        editor.buffer().update(cx, |multibuffer, cx| {
20583            let buffer = multibuffer.as_singleton().unwrap();
20584            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
20585
20586            multibuffer.set_all_diff_hunks_expanded(cx);
20587            multibuffer.add_diff(diff, cx);
20588
20589            buffer.read(cx).remote_id()
20590        })
20591    });
20592    cx.run_until_parked();
20593
20594    cx.assert_state_with_diff(
20595        indoc! { "
20596          impl A {
20597              fn b() {
20598                  0;
20599        -         1;
20600        -         2;
20601                  3;
20602        -         4;
20603        -     }
20604        -     fn c() {
20605                  5;
20606                  6;
20607                  7;
20608              }
20609          }
20610          ˇ"
20611        }
20612        .to_string(),
20613    );
20614
20615    let mut actual_guides = cx.update_editor(|editor, window, cx| {
20616        editor
20617            .snapshot(window, cx)
20618            .buffer_snapshot
20619            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
20620            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
20621            .collect::<Vec<_>>()
20622    });
20623    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
20624    assert_eq!(
20625        actual_guides,
20626        vec![
20627            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
20628            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
20629            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
20630        ]
20631    );
20632}
20633
20634#[gpui::test]
20635async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20636    init_test(cx, |_| {});
20637    let mut cx = EditorTestContext::new(cx).await;
20638
20639    let diff_base = r#"
20640        a
20641        b
20642        c
20643        "#
20644    .unindent();
20645
20646    cx.set_state(
20647        &r#"
20648        ˇA
20649        b
20650        C
20651        "#
20652        .unindent(),
20653    );
20654    cx.set_head_text(&diff_base);
20655    cx.update_editor(|editor, window, cx| {
20656        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20657    });
20658    executor.run_until_parked();
20659
20660    let both_hunks_expanded = r#"
20661        - a
20662        + ˇA
20663          b
20664        - c
20665        + C
20666        "#
20667    .unindent();
20668
20669    cx.assert_state_with_diff(both_hunks_expanded.clone());
20670
20671    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20672        let snapshot = editor.snapshot(window, cx);
20673        let hunks = editor
20674            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20675            .collect::<Vec<_>>();
20676        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20677        let buffer_id = hunks[0].buffer_id;
20678        hunks
20679            .into_iter()
20680            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20681            .collect::<Vec<_>>()
20682    });
20683    assert_eq!(hunk_ranges.len(), 2);
20684
20685    cx.update_editor(|editor, _, cx| {
20686        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20687    });
20688    executor.run_until_parked();
20689
20690    let second_hunk_expanded = r#"
20691          ˇA
20692          b
20693        - c
20694        + C
20695        "#
20696    .unindent();
20697
20698    cx.assert_state_with_diff(second_hunk_expanded);
20699
20700    cx.update_editor(|editor, _, cx| {
20701        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20702    });
20703    executor.run_until_parked();
20704
20705    cx.assert_state_with_diff(both_hunks_expanded.clone());
20706
20707    cx.update_editor(|editor, _, cx| {
20708        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20709    });
20710    executor.run_until_parked();
20711
20712    let first_hunk_expanded = r#"
20713        - a
20714        + ˇA
20715          b
20716          C
20717        "#
20718    .unindent();
20719
20720    cx.assert_state_with_diff(first_hunk_expanded);
20721
20722    cx.update_editor(|editor, _, cx| {
20723        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20724    });
20725    executor.run_until_parked();
20726
20727    cx.assert_state_with_diff(both_hunks_expanded);
20728
20729    cx.set_state(
20730        &r#"
20731        ˇA
20732        b
20733        "#
20734        .unindent(),
20735    );
20736    cx.run_until_parked();
20737
20738    // TODO this cursor position seems bad
20739    cx.assert_state_with_diff(
20740        r#"
20741        - ˇa
20742        + A
20743          b
20744        "#
20745        .unindent(),
20746    );
20747
20748    cx.update_editor(|editor, window, cx| {
20749        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20750    });
20751
20752    cx.assert_state_with_diff(
20753        r#"
20754            - ˇa
20755            + A
20756              b
20757            - c
20758            "#
20759        .unindent(),
20760    );
20761
20762    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20763        let snapshot = editor.snapshot(window, cx);
20764        let hunks = editor
20765            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20766            .collect::<Vec<_>>();
20767        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20768        let buffer_id = hunks[0].buffer_id;
20769        hunks
20770            .into_iter()
20771            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20772            .collect::<Vec<_>>()
20773    });
20774    assert_eq!(hunk_ranges.len(), 2);
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    cx.assert_state_with_diff(
20782        r#"
20783        - ˇa
20784        + A
20785          b
20786        "#
20787        .unindent(),
20788    );
20789}
20790
20791#[gpui::test]
20792async fn test_toggle_deletion_hunk_at_start_of_file(
20793    executor: BackgroundExecutor,
20794    cx: &mut TestAppContext,
20795) {
20796    init_test(cx, |_| {});
20797    let mut cx = EditorTestContext::new(cx).await;
20798
20799    let diff_base = r#"
20800        a
20801        b
20802        c
20803        "#
20804    .unindent();
20805
20806    cx.set_state(
20807        &r#"
20808        ˇb
20809        c
20810        "#
20811        .unindent(),
20812    );
20813    cx.set_head_text(&diff_base);
20814    cx.update_editor(|editor, window, cx| {
20815        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20816    });
20817    executor.run_until_parked();
20818
20819    let hunk_expanded = r#"
20820        - a
20821          ˇb
20822          c
20823        "#
20824    .unindent();
20825
20826    cx.assert_state_with_diff(hunk_expanded.clone());
20827
20828    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20829        let snapshot = editor.snapshot(window, cx);
20830        let hunks = editor
20831            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20832            .collect::<Vec<_>>();
20833        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20834        let buffer_id = hunks[0].buffer_id;
20835        hunks
20836            .into_iter()
20837            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20838            .collect::<Vec<_>>()
20839    });
20840    assert_eq!(hunk_ranges.len(), 1);
20841
20842    cx.update_editor(|editor, _, cx| {
20843        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20844    });
20845    executor.run_until_parked();
20846
20847    let hunk_collapsed = r#"
20848          ˇb
20849          c
20850        "#
20851    .unindent();
20852
20853    cx.assert_state_with_diff(hunk_collapsed);
20854
20855    cx.update_editor(|editor, _, cx| {
20856        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20857    });
20858    executor.run_until_parked();
20859
20860    cx.assert_state_with_diff(hunk_expanded);
20861}
20862
20863#[gpui::test]
20864async fn test_display_diff_hunks(cx: &mut TestAppContext) {
20865    init_test(cx, |_| {});
20866
20867    let fs = FakeFs::new(cx.executor());
20868    fs.insert_tree(
20869        path!("/test"),
20870        json!({
20871            ".git": {},
20872            "file-1": "ONE\n",
20873            "file-2": "TWO\n",
20874            "file-3": "THREE\n",
20875        }),
20876    )
20877    .await;
20878
20879    fs.set_head_for_repo(
20880        path!("/test/.git").as_ref(),
20881        &[
20882            ("file-1", "one\n".into()),
20883            ("file-2", "two\n".into()),
20884            ("file-3", "three\n".into()),
20885        ],
20886        "deadbeef",
20887    );
20888
20889    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
20890    let mut buffers = vec![];
20891    for i in 1..=3 {
20892        let buffer = project
20893            .update(cx, |project, cx| {
20894                let path = format!(path!("/test/file-{}"), i);
20895                project.open_local_buffer(path, cx)
20896            })
20897            .await
20898            .unwrap();
20899        buffers.push(buffer);
20900    }
20901
20902    let multibuffer = cx.new(|cx| {
20903        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
20904        multibuffer.set_all_diff_hunks_expanded(cx);
20905        for buffer in &buffers {
20906            let snapshot = buffer.read(cx).snapshot();
20907            multibuffer.set_excerpts_for_path(
20908                PathKey::namespaced(
20909                    0,
20910                    buffer.read(cx).file().unwrap().path().as_unix_str().into(),
20911                ),
20912                buffer.clone(),
20913                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
20914                2,
20915                cx,
20916            );
20917        }
20918        multibuffer
20919    });
20920
20921    let editor = cx.add_window(|window, cx| {
20922        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
20923    });
20924    cx.run_until_parked();
20925
20926    let snapshot = editor
20927        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
20928        .unwrap();
20929    let hunks = snapshot
20930        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
20931        .map(|hunk| match hunk {
20932            DisplayDiffHunk::Unfolded {
20933                display_row_range, ..
20934            } => display_row_range,
20935            DisplayDiffHunk::Folded { .. } => unreachable!(),
20936        })
20937        .collect::<Vec<_>>();
20938    assert_eq!(
20939        hunks,
20940        [
20941            DisplayRow(2)..DisplayRow(4),
20942            DisplayRow(7)..DisplayRow(9),
20943            DisplayRow(12)..DisplayRow(14),
20944        ]
20945    );
20946}
20947
20948#[gpui::test]
20949async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
20950    init_test(cx, |_| {});
20951
20952    let mut cx = EditorTestContext::new(cx).await;
20953    cx.set_head_text(indoc! { "
20954        one
20955        two
20956        three
20957        four
20958        five
20959        "
20960    });
20961    cx.set_index_text(indoc! { "
20962        one
20963        two
20964        three
20965        four
20966        five
20967        "
20968    });
20969    cx.set_state(indoc! {"
20970        one
20971        TWO
20972        ˇTHREE
20973        FOUR
20974        five
20975    "});
20976    cx.run_until_parked();
20977    cx.update_editor(|editor, window, cx| {
20978        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20979    });
20980    cx.run_until_parked();
20981    cx.assert_index_text(Some(indoc! {"
20982        one
20983        TWO
20984        THREE
20985        FOUR
20986        five
20987    "}));
20988    cx.set_state(indoc! { "
20989        one
20990        TWO
20991        ˇTHREE-HUNDRED
20992        FOUR
20993        five
20994    "});
20995    cx.run_until_parked();
20996    cx.update_editor(|editor, window, cx| {
20997        let snapshot = editor.snapshot(window, cx);
20998        let hunks = editor
20999            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
21000            .collect::<Vec<_>>();
21001        assert_eq!(hunks.len(), 1);
21002        assert_eq!(
21003            hunks[0].status(),
21004            DiffHunkStatus {
21005                kind: DiffHunkStatusKind::Modified,
21006                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21007            }
21008        );
21009
21010        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21011    });
21012    cx.run_until_parked();
21013    cx.assert_index_text(Some(indoc! {"
21014        one
21015        TWO
21016        THREE-HUNDRED
21017        FOUR
21018        five
21019    "}));
21020}
21021
21022#[gpui::test]
21023fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21024    init_test(cx, |_| {});
21025
21026    let editor = cx.add_window(|window, cx| {
21027        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21028        build_editor(buffer, window, cx)
21029    });
21030
21031    let render_args = Arc::new(Mutex::new(None));
21032    let snapshot = editor
21033        .update(cx, |editor, window, cx| {
21034            let snapshot = editor.buffer().read(cx).snapshot(cx);
21035            let range =
21036                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21037
21038            struct RenderArgs {
21039                row: MultiBufferRow,
21040                folded: bool,
21041                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21042            }
21043
21044            let crease = Crease::inline(
21045                range,
21046                FoldPlaceholder::test(),
21047                {
21048                    let toggle_callback = render_args.clone();
21049                    move |row, folded, callback, _window, _cx| {
21050                        *toggle_callback.lock() = Some(RenderArgs {
21051                            row,
21052                            folded,
21053                            callback,
21054                        });
21055                        div()
21056                    }
21057                },
21058                |_row, _folded, _window, _cx| div(),
21059            );
21060
21061            editor.insert_creases(Some(crease), cx);
21062            let snapshot = editor.snapshot(window, cx);
21063            let _div =
21064                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21065            snapshot
21066        })
21067        .unwrap();
21068
21069    let render_args = render_args.lock().take().unwrap();
21070    assert_eq!(render_args.row, MultiBufferRow(1));
21071    assert!(!render_args.folded);
21072    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21073
21074    cx.update_window(*editor, |_, window, cx| {
21075        (render_args.callback)(true, window, cx)
21076    })
21077    .unwrap();
21078    let snapshot = editor
21079        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21080        .unwrap();
21081    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21082
21083    cx.update_window(*editor, |_, window, cx| {
21084        (render_args.callback)(false, window, cx)
21085    })
21086    .unwrap();
21087    let snapshot = editor
21088        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21089        .unwrap();
21090    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21091}
21092
21093#[gpui::test]
21094async fn test_input_text(cx: &mut TestAppContext) {
21095    init_test(cx, |_| {});
21096    let mut cx = EditorTestContext::new(cx).await;
21097
21098    cx.set_state(
21099        &r#"ˇone
21100        two
21101
21102        three
21103        fourˇ
21104        five
21105
21106        siˇx"#
21107            .unindent(),
21108    );
21109
21110    cx.dispatch_action(HandleInput(String::new()));
21111    cx.assert_editor_state(
21112        &r#"ˇone
21113        two
21114
21115        three
21116        fourˇ
21117        five
21118
21119        siˇx"#
21120            .unindent(),
21121    );
21122
21123    cx.dispatch_action(HandleInput("AAAA".to_string()));
21124    cx.assert_editor_state(
21125        &r#"AAAAˇone
21126        two
21127
21128        three
21129        fourAAAAˇ
21130        five
21131
21132        siAAAAˇx"#
21133            .unindent(),
21134    );
21135}
21136
21137#[gpui::test]
21138async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
21139    init_test(cx, |_| {});
21140
21141    let mut cx = EditorTestContext::new(cx).await;
21142    cx.set_state(
21143        r#"let foo = 1;
21144let foo = 2;
21145let foo = 3;
21146let fooˇ = 4;
21147let foo = 5;
21148let foo = 6;
21149let foo = 7;
21150let foo = 8;
21151let foo = 9;
21152let foo = 10;
21153let foo = 11;
21154let foo = 12;
21155let foo = 13;
21156let foo = 14;
21157let foo = 15;"#,
21158    );
21159
21160    cx.update_editor(|e, window, cx| {
21161        assert_eq!(
21162            e.next_scroll_position,
21163            NextScrollCursorCenterTopBottom::Center,
21164            "Default next scroll direction is center",
21165        );
21166
21167        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21168        assert_eq!(
21169            e.next_scroll_position,
21170            NextScrollCursorCenterTopBottom::Top,
21171            "After center, next scroll direction should be top",
21172        );
21173
21174        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21175        assert_eq!(
21176            e.next_scroll_position,
21177            NextScrollCursorCenterTopBottom::Bottom,
21178            "After top, next scroll direction should be bottom",
21179        );
21180
21181        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21182        assert_eq!(
21183            e.next_scroll_position,
21184            NextScrollCursorCenterTopBottom::Center,
21185            "After bottom, scrolling should start over",
21186        );
21187
21188        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21189        assert_eq!(
21190            e.next_scroll_position,
21191            NextScrollCursorCenterTopBottom::Top,
21192            "Scrolling continues if retriggered fast enough"
21193        );
21194    });
21195
21196    cx.executor()
21197        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
21198    cx.executor().run_until_parked();
21199    cx.update_editor(|e, _, _| {
21200        assert_eq!(
21201            e.next_scroll_position,
21202            NextScrollCursorCenterTopBottom::Center,
21203            "If scrolling is not triggered fast enough, it should reset"
21204        );
21205    });
21206}
21207
21208#[gpui::test]
21209async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
21210    init_test(cx, |_| {});
21211    let mut cx = EditorLspTestContext::new_rust(
21212        lsp::ServerCapabilities {
21213            definition_provider: Some(lsp::OneOf::Left(true)),
21214            references_provider: Some(lsp::OneOf::Left(true)),
21215            ..lsp::ServerCapabilities::default()
21216        },
21217        cx,
21218    )
21219    .await;
21220
21221    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21222        let go_to_definition = cx
21223            .lsp
21224            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21225                move |params, _| async move {
21226                    if empty_go_to_definition {
21227                        Ok(None)
21228                    } else {
21229                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21230                            uri: params.text_document_position_params.text_document.uri,
21231                            range: lsp::Range::new(
21232                                lsp::Position::new(4, 3),
21233                                lsp::Position::new(4, 6),
21234                            ),
21235                        })))
21236                    }
21237                },
21238            );
21239        let references = cx
21240            .lsp
21241            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21242                Ok(Some(vec![lsp::Location {
21243                    uri: params.text_document_position.text_document.uri,
21244                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21245                }]))
21246            });
21247        (go_to_definition, references)
21248    };
21249
21250    cx.set_state(
21251        &r#"fn one() {
21252            let mut a = ˇtwo();
21253        }
21254
21255        fn two() {}"#
21256            .unindent(),
21257    );
21258    set_up_lsp_handlers(false, &mut cx);
21259    let navigated = cx
21260        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21261        .await
21262        .expect("Failed to navigate to definition");
21263    assert_eq!(
21264        navigated,
21265        Navigated::Yes,
21266        "Should have navigated to definition from the GetDefinition response"
21267    );
21268    cx.assert_editor_state(
21269        &r#"fn one() {
21270            let mut a = two();
21271        }
21272
21273        fn «twoˇ»() {}"#
21274            .unindent(),
21275    );
21276
21277    let editors = cx.update_workspace(|workspace, _, cx| {
21278        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21279    });
21280    cx.update_editor(|_, _, test_editor_cx| {
21281        assert_eq!(
21282            editors.len(),
21283            1,
21284            "Initially, only one, test, editor should be open in the workspace"
21285        );
21286        assert_eq!(
21287            test_editor_cx.entity(),
21288            editors.last().expect("Asserted len is 1").clone()
21289        );
21290    });
21291
21292    set_up_lsp_handlers(true, &mut cx);
21293    let navigated = cx
21294        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21295        .await
21296        .expect("Failed to navigate to lookup references");
21297    assert_eq!(
21298        navigated,
21299        Navigated::Yes,
21300        "Should have navigated to references as a fallback after empty GoToDefinition response"
21301    );
21302    // We should not change the selections in the existing file,
21303    // if opening another milti buffer with the references
21304    cx.assert_editor_state(
21305        &r#"fn one() {
21306            let mut a = two();
21307        }
21308
21309        fn «twoˇ»() {}"#
21310            .unindent(),
21311    );
21312    let editors = cx.update_workspace(|workspace, _, cx| {
21313        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21314    });
21315    cx.update_editor(|_, _, test_editor_cx| {
21316        assert_eq!(
21317            editors.len(),
21318            2,
21319            "After falling back to references search, we open a new editor with the results"
21320        );
21321        let references_fallback_text = editors
21322            .into_iter()
21323            .find(|new_editor| *new_editor != test_editor_cx.entity())
21324            .expect("Should have one non-test editor now")
21325            .read(test_editor_cx)
21326            .text(test_editor_cx);
21327        assert_eq!(
21328            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21329            "Should use the range from the references response and not the GoToDefinition one"
21330        );
21331    });
21332}
21333
21334#[gpui::test]
21335async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21336    init_test(cx, |_| {});
21337    cx.update(|cx| {
21338        let mut editor_settings = EditorSettings::get_global(cx).clone();
21339        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21340        EditorSettings::override_global(editor_settings, cx);
21341    });
21342    let mut cx = EditorLspTestContext::new_rust(
21343        lsp::ServerCapabilities {
21344            definition_provider: Some(lsp::OneOf::Left(true)),
21345            references_provider: Some(lsp::OneOf::Left(true)),
21346            ..lsp::ServerCapabilities::default()
21347        },
21348        cx,
21349    )
21350    .await;
21351    let original_state = r#"fn one() {
21352        let mut a = ˇtwo();
21353    }
21354
21355    fn two() {}"#
21356        .unindent();
21357    cx.set_state(&original_state);
21358
21359    let mut go_to_definition = cx
21360        .lsp
21361        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21362            move |_, _| async move { Ok(None) },
21363        );
21364    let _references = cx
21365        .lsp
21366        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21367            panic!("Should not call for references with no go to definition fallback")
21368        });
21369
21370    let navigated = cx
21371        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21372        .await
21373        .expect("Failed to navigate to lookup references");
21374    go_to_definition
21375        .next()
21376        .await
21377        .expect("Should have called the go_to_definition handler");
21378
21379    assert_eq!(
21380        navigated,
21381        Navigated::No,
21382        "Should have navigated to references as a fallback after empty GoToDefinition response"
21383    );
21384    cx.assert_editor_state(&original_state);
21385    let editors = cx.update_workspace(|workspace, _, cx| {
21386        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21387    });
21388    cx.update_editor(|_, _, _| {
21389        assert_eq!(
21390            editors.len(),
21391            1,
21392            "After unsuccessful fallback, no other editor should have been opened"
21393        );
21394    });
21395}
21396
21397#[gpui::test]
21398async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
21399    init_test(cx, |_| {});
21400    let mut cx = EditorLspTestContext::new_rust(
21401        lsp::ServerCapabilities {
21402            references_provider: Some(lsp::OneOf::Left(true)),
21403            ..lsp::ServerCapabilities::default()
21404        },
21405        cx,
21406    )
21407    .await;
21408
21409    cx.set_state(
21410        &r#"
21411        fn one() {
21412            let mut a = two();
21413        }
21414
21415        fn ˇtwo() {}"#
21416            .unindent(),
21417    );
21418    cx.lsp
21419        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21420            Ok(Some(vec![
21421                lsp::Location {
21422                    uri: params.text_document_position.text_document.uri.clone(),
21423                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21424                },
21425                lsp::Location {
21426                    uri: params.text_document_position.text_document.uri,
21427                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
21428                },
21429            ]))
21430        });
21431    let navigated = cx
21432        .update_editor(|editor, window, cx| {
21433            editor.find_all_references(&FindAllReferences, window, cx)
21434        })
21435        .unwrap()
21436        .await
21437        .expect("Failed to navigate to references");
21438    assert_eq!(
21439        navigated,
21440        Navigated::Yes,
21441        "Should have navigated to references from the FindAllReferences response"
21442    );
21443    cx.assert_editor_state(
21444        &r#"fn one() {
21445            let mut a = two();
21446        }
21447
21448        fn ˇtwo() {}"#
21449            .unindent(),
21450    );
21451
21452    let editors = cx.update_workspace(|workspace, _, cx| {
21453        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21454    });
21455    cx.update_editor(|_, _, _| {
21456        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
21457    });
21458
21459    cx.set_state(
21460        &r#"fn one() {
21461            let mut a = ˇtwo();
21462        }
21463
21464        fn two() {}"#
21465            .unindent(),
21466    );
21467    let navigated = cx
21468        .update_editor(|editor, window, cx| {
21469            editor.find_all_references(&FindAllReferences, window, cx)
21470        })
21471        .unwrap()
21472        .await
21473        .expect("Failed to navigate to references");
21474    assert_eq!(
21475        navigated,
21476        Navigated::Yes,
21477        "Should have navigated to references from the FindAllReferences response"
21478    );
21479    cx.assert_editor_state(
21480        &r#"fn one() {
21481            let mut a = ˇtwo();
21482        }
21483
21484        fn two() {}"#
21485            .unindent(),
21486    );
21487    let editors = cx.update_workspace(|workspace, _, cx| {
21488        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21489    });
21490    cx.update_editor(|_, _, _| {
21491        assert_eq!(
21492            editors.len(),
21493            2,
21494            "should have re-used the previous multibuffer"
21495        );
21496    });
21497
21498    cx.set_state(
21499        &r#"fn one() {
21500            let mut a = ˇtwo();
21501        }
21502        fn three() {}
21503        fn two() {}"#
21504            .unindent(),
21505    );
21506    cx.lsp
21507        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21508            Ok(Some(vec![
21509                lsp::Location {
21510                    uri: params.text_document_position.text_document.uri.clone(),
21511                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21512                },
21513                lsp::Location {
21514                    uri: params.text_document_position.text_document.uri,
21515                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
21516                },
21517            ]))
21518        });
21519    let navigated = cx
21520        .update_editor(|editor, window, cx| {
21521            editor.find_all_references(&FindAllReferences, window, cx)
21522        })
21523        .unwrap()
21524        .await
21525        .expect("Failed to navigate to references");
21526    assert_eq!(
21527        navigated,
21528        Navigated::Yes,
21529        "Should have navigated to references from the FindAllReferences response"
21530    );
21531    cx.assert_editor_state(
21532        &r#"fn one() {
21533                let mut a = ˇtwo();
21534            }
21535            fn three() {}
21536            fn two() {}"#
21537            .unindent(),
21538    );
21539    let editors = cx.update_workspace(|workspace, _, cx| {
21540        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21541    });
21542    cx.update_editor(|_, _, _| {
21543        assert_eq!(
21544            editors.len(),
21545            3,
21546            "should have used a new multibuffer as offsets changed"
21547        );
21548    });
21549}
21550#[gpui::test]
21551async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
21552    init_test(cx, |_| {});
21553
21554    let language = Arc::new(Language::new(
21555        LanguageConfig::default(),
21556        Some(tree_sitter_rust::LANGUAGE.into()),
21557    ));
21558
21559    let text = r#"
21560        #[cfg(test)]
21561        mod tests() {
21562            #[test]
21563            fn runnable_1() {
21564                let a = 1;
21565            }
21566
21567            #[test]
21568            fn runnable_2() {
21569                let a = 1;
21570                let b = 2;
21571            }
21572        }
21573    "#
21574    .unindent();
21575
21576    let fs = FakeFs::new(cx.executor());
21577    fs.insert_file("/file.rs", Default::default()).await;
21578
21579    let project = Project::test(fs, ["/a".as_ref()], cx).await;
21580    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21581    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21582    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
21583    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
21584
21585    let editor = cx.new_window_entity(|window, cx| {
21586        Editor::new(
21587            EditorMode::full(),
21588            multi_buffer,
21589            Some(project.clone()),
21590            window,
21591            cx,
21592        )
21593    });
21594
21595    editor.update_in(cx, |editor, window, cx| {
21596        let snapshot = editor.buffer().read(cx).snapshot(cx);
21597        editor.tasks.insert(
21598            (buffer.read(cx).remote_id(), 3),
21599            RunnableTasks {
21600                templates: vec![],
21601                offset: snapshot.anchor_before(43),
21602                column: 0,
21603                extra_variables: HashMap::default(),
21604                context_range: BufferOffset(43)..BufferOffset(85),
21605            },
21606        );
21607        editor.tasks.insert(
21608            (buffer.read(cx).remote_id(), 8),
21609            RunnableTasks {
21610                templates: vec![],
21611                offset: snapshot.anchor_before(86),
21612                column: 0,
21613                extra_variables: HashMap::default(),
21614                context_range: BufferOffset(86)..BufferOffset(191),
21615            },
21616        );
21617
21618        // Test finding task when cursor is inside function body
21619        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21620            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
21621        });
21622        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21623        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
21624
21625        // Test finding task when cursor is on function name
21626        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21627            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
21628        });
21629        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21630        assert_eq!(row, 8, "Should find task when cursor is on function name");
21631    });
21632}
21633
21634#[gpui::test]
21635async fn test_folding_buffers(cx: &mut TestAppContext) {
21636    init_test(cx, |_| {});
21637
21638    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21639    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
21640    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
21641
21642    let fs = FakeFs::new(cx.executor());
21643    fs.insert_tree(
21644        path!("/a"),
21645        json!({
21646            "first.rs": sample_text_1,
21647            "second.rs": sample_text_2,
21648            "third.rs": sample_text_3,
21649        }),
21650    )
21651    .await;
21652    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21653    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21654    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21655    let worktree = project.update(cx, |project, cx| {
21656        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21657        assert_eq!(worktrees.len(), 1);
21658        worktrees.pop().unwrap()
21659    });
21660    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21661
21662    let buffer_1 = project
21663        .update(cx, |project, cx| {
21664            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
21665        })
21666        .await
21667        .unwrap();
21668    let buffer_2 = project
21669        .update(cx, |project, cx| {
21670            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
21671        })
21672        .await
21673        .unwrap();
21674    let buffer_3 = project
21675        .update(cx, |project, cx| {
21676            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
21677        })
21678        .await
21679        .unwrap();
21680
21681    let multi_buffer = cx.new(|cx| {
21682        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21683        multi_buffer.push_excerpts(
21684            buffer_1.clone(),
21685            [
21686                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21687                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21688                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21689            ],
21690            cx,
21691        );
21692        multi_buffer.push_excerpts(
21693            buffer_2.clone(),
21694            [
21695                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21696                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21697                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21698            ],
21699            cx,
21700        );
21701        multi_buffer.push_excerpts(
21702            buffer_3.clone(),
21703            [
21704                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21705                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21706                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21707            ],
21708            cx,
21709        );
21710        multi_buffer
21711    });
21712    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21713        Editor::new(
21714            EditorMode::full(),
21715            multi_buffer.clone(),
21716            Some(project.clone()),
21717            window,
21718            cx,
21719        )
21720    });
21721
21722    assert_eq!(
21723        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21724        "\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",
21725    );
21726
21727    multi_buffer_editor.update(cx, |editor, cx| {
21728        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21729    });
21730    assert_eq!(
21731        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21732        "\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",
21733        "After folding the first buffer, its text should not be displayed"
21734    );
21735
21736    multi_buffer_editor.update(cx, |editor, cx| {
21737        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21738    });
21739    assert_eq!(
21740        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21741        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
21742        "After folding the second buffer, its text should not be displayed"
21743    );
21744
21745    multi_buffer_editor.update(cx, |editor, cx| {
21746        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21747    });
21748    assert_eq!(
21749        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21750        "\n\n\n\n\n",
21751        "After folding the third buffer, its text should not be displayed"
21752    );
21753
21754    // Emulate selection inside the fold logic, that should work
21755    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21756        editor
21757            .snapshot(window, cx)
21758            .next_line_boundary(Point::new(0, 4));
21759    });
21760
21761    multi_buffer_editor.update(cx, |editor, cx| {
21762        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21763    });
21764    assert_eq!(
21765        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21766        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21767        "After unfolding the second buffer, its text should be displayed"
21768    );
21769
21770    // Typing inside of buffer 1 causes that buffer to be unfolded.
21771    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21772        assert_eq!(
21773            multi_buffer
21774                .read(cx)
21775                .snapshot(cx)
21776                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
21777                .collect::<String>(),
21778            "bbbb"
21779        );
21780        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21781            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
21782        });
21783        editor.handle_input("B", window, cx);
21784    });
21785
21786    assert_eq!(
21787        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21788        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21789        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
21790    );
21791
21792    multi_buffer_editor.update(cx, |editor, cx| {
21793        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21794    });
21795    assert_eq!(
21796        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21797        "\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",
21798        "After unfolding the all buffers, all original text should be displayed"
21799    );
21800}
21801
21802#[gpui::test]
21803async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
21804    init_test(cx, |_| {});
21805
21806    let sample_text_1 = "1111\n2222\n3333".to_string();
21807    let sample_text_2 = "4444\n5555\n6666".to_string();
21808    let sample_text_3 = "7777\n8888\n9999".to_string();
21809
21810    let fs = FakeFs::new(cx.executor());
21811    fs.insert_tree(
21812        path!("/a"),
21813        json!({
21814            "first.rs": sample_text_1,
21815            "second.rs": sample_text_2,
21816            "third.rs": sample_text_3,
21817        }),
21818    )
21819    .await;
21820    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21821    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21822    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21823    let worktree = project.update(cx, |project, cx| {
21824        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21825        assert_eq!(worktrees.len(), 1);
21826        worktrees.pop().unwrap()
21827    });
21828    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21829
21830    let buffer_1 = project
21831        .update(cx, |project, cx| {
21832            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
21833        })
21834        .await
21835        .unwrap();
21836    let buffer_2 = project
21837        .update(cx, |project, cx| {
21838            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
21839        })
21840        .await
21841        .unwrap();
21842    let buffer_3 = project
21843        .update(cx, |project, cx| {
21844            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
21845        })
21846        .await
21847        .unwrap();
21848
21849    let multi_buffer = cx.new(|cx| {
21850        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21851        multi_buffer.push_excerpts(
21852            buffer_1.clone(),
21853            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21854            cx,
21855        );
21856        multi_buffer.push_excerpts(
21857            buffer_2.clone(),
21858            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21859            cx,
21860        );
21861        multi_buffer.push_excerpts(
21862            buffer_3.clone(),
21863            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21864            cx,
21865        );
21866        multi_buffer
21867    });
21868
21869    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21870        Editor::new(
21871            EditorMode::full(),
21872            multi_buffer,
21873            Some(project.clone()),
21874            window,
21875            cx,
21876        )
21877    });
21878
21879    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
21880    assert_eq!(
21881        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21882        full_text,
21883    );
21884
21885    multi_buffer_editor.update(cx, |editor, cx| {
21886        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21887    });
21888    assert_eq!(
21889        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21890        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
21891        "After folding the first buffer, its text should not be displayed"
21892    );
21893
21894    multi_buffer_editor.update(cx, |editor, cx| {
21895        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21896    });
21897
21898    assert_eq!(
21899        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21900        "\n\n\n\n\n\n7777\n8888\n9999",
21901        "After folding the second buffer, its text should not be displayed"
21902    );
21903
21904    multi_buffer_editor.update(cx, |editor, cx| {
21905        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21906    });
21907    assert_eq!(
21908        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21909        "\n\n\n\n\n",
21910        "After folding the third buffer, its text should not be displayed"
21911    );
21912
21913    multi_buffer_editor.update(cx, |editor, cx| {
21914        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21915    });
21916    assert_eq!(
21917        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21918        "\n\n\n\n4444\n5555\n6666\n\n",
21919        "After unfolding the second buffer, its text should be displayed"
21920    );
21921
21922    multi_buffer_editor.update(cx, |editor, cx| {
21923        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
21924    });
21925    assert_eq!(
21926        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21927        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
21928        "After unfolding the first buffer, its text should be displayed"
21929    );
21930
21931    multi_buffer_editor.update(cx, |editor, cx| {
21932        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21933    });
21934    assert_eq!(
21935        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21936        full_text,
21937        "After unfolding all buffers, all original text should be displayed"
21938    );
21939}
21940
21941#[gpui::test]
21942async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
21943    init_test(cx, |_| {});
21944
21945    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21946
21947    let fs = FakeFs::new(cx.executor());
21948    fs.insert_tree(
21949        path!("/a"),
21950        json!({
21951            "main.rs": sample_text,
21952        }),
21953    )
21954    .await;
21955    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21956    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21957    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21958    let worktree = project.update(cx, |project, cx| {
21959        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21960        assert_eq!(worktrees.len(), 1);
21961        worktrees.pop().unwrap()
21962    });
21963    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21964
21965    let buffer_1 = project
21966        .update(cx, |project, cx| {
21967            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
21968        })
21969        .await
21970        .unwrap();
21971
21972    let multi_buffer = cx.new(|cx| {
21973        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21974        multi_buffer.push_excerpts(
21975            buffer_1.clone(),
21976            [ExcerptRange::new(
21977                Point::new(0, 0)
21978                    ..Point::new(
21979                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
21980                        0,
21981                    ),
21982            )],
21983            cx,
21984        );
21985        multi_buffer
21986    });
21987    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21988        Editor::new(
21989            EditorMode::full(),
21990            multi_buffer,
21991            Some(project.clone()),
21992            window,
21993            cx,
21994        )
21995    });
21996
21997    let selection_range = Point::new(1, 0)..Point::new(2, 0);
21998    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21999        enum TestHighlight {}
22000        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22001        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
22002        editor.highlight_text::<TestHighlight>(
22003            vec![highlight_range.clone()],
22004            HighlightStyle::color(Hsla::green()),
22005            cx,
22006        );
22007        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22008            s.select_ranges(Some(highlight_range))
22009        });
22010    });
22011
22012    let full_text = format!("\n\n{sample_text}");
22013    assert_eq!(
22014        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22015        full_text,
22016    );
22017}
22018
22019#[gpui::test]
22020async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22021    init_test(cx, |_| {});
22022    cx.update(|cx| {
22023        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22024            "keymaps/default-linux.json",
22025            cx,
22026        )
22027        .unwrap();
22028        cx.bind_keys(default_key_bindings);
22029    });
22030
22031    let (editor, cx) = cx.add_window_view(|window, cx| {
22032        let multi_buffer = MultiBuffer::build_multi(
22033            [
22034                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22035                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22036                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22037                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22038            ],
22039            cx,
22040        );
22041        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22042
22043        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22044        // fold all but the second buffer, so that we test navigating between two
22045        // adjacent folded buffers, as well as folded buffers at the start and
22046        // end the multibuffer
22047        editor.fold_buffer(buffer_ids[0], cx);
22048        editor.fold_buffer(buffer_ids[2], cx);
22049        editor.fold_buffer(buffer_ids[3], cx);
22050
22051        editor
22052    });
22053    cx.simulate_resize(size(px(1000.), px(1000.)));
22054
22055    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22056    cx.assert_excerpts_with_selections(indoc! {"
22057        [EXCERPT]
22058        ˇ[FOLDED]
22059        [EXCERPT]
22060        a1
22061        b1
22062        [EXCERPT]
22063        [FOLDED]
22064        [EXCERPT]
22065        [FOLDED]
22066        "
22067    });
22068    cx.simulate_keystroke("down");
22069    cx.assert_excerpts_with_selections(indoc! {"
22070        [EXCERPT]
22071        [FOLDED]
22072        [EXCERPT]
22073        ˇa1
22074        b1
22075        [EXCERPT]
22076        [FOLDED]
22077        [EXCERPT]
22078        [FOLDED]
22079        "
22080    });
22081    cx.simulate_keystroke("down");
22082    cx.assert_excerpts_with_selections(indoc! {"
22083        [EXCERPT]
22084        [FOLDED]
22085        [EXCERPT]
22086        a1
22087        ˇb1
22088        [EXCERPT]
22089        [FOLDED]
22090        [EXCERPT]
22091        [FOLDED]
22092        "
22093    });
22094    cx.simulate_keystroke("down");
22095    cx.assert_excerpts_with_selections(indoc! {"
22096        [EXCERPT]
22097        [FOLDED]
22098        [EXCERPT]
22099        a1
22100        b1
22101        ˇ[EXCERPT]
22102        [FOLDED]
22103        [EXCERPT]
22104        [FOLDED]
22105        "
22106    });
22107    cx.simulate_keystroke("down");
22108    cx.assert_excerpts_with_selections(indoc! {"
22109        [EXCERPT]
22110        [FOLDED]
22111        [EXCERPT]
22112        a1
22113        b1
22114        [EXCERPT]
22115        ˇ[FOLDED]
22116        [EXCERPT]
22117        [FOLDED]
22118        "
22119    });
22120    for _ in 0..5 {
22121        cx.simulate_keystroke("down");
22122        cx.assert_excerpts_with_selections(indoc! {"
22123            [EXCERPT]
22124            [FOLDED]
22125            [EXCERPT]
22126            a1
22127            b1
22128            [EXCERPT]
22129            [FOLDED]
22130            [EXCERPT]
22131            ˇ[FOLDED]
22132            "
22133        });
22134    }
22135
22136    cx.simulate_keystroke("up");
22137    cx.assert_excerpts_with_selections(indoc! {"
22138        [EXCERPT]
22139        [FOLDED]
22140        [EXCERPT]
22141        a1
22142        b1
22143        [EXCERPT]
22144        ˇ[FOLDED]
22145        [EXCERPT]
22146        [FOLDED]
22147        "
22148    });
22149    cx.simulate_keystroke("up");
22150    cx.assert_excerpts_with_selections(indoc! {"
22151        [EXCERPT]
22152        [FOLDED]
22153        [EXCERPT]
22154        a1
22155        b1
22156        ˇ[EXCERPT]
22157        [FOLDED]
22158        [EXCERPT]
22159        [FOLDED]
22160        "
22161    });
22162    cx.simulate_keystroke("up");
22163    cx.assert_excerpts_with_selections(indoc! {"
22164        [EXCERPT]
22165        [FOLDED]
22166        [EXCERPT]
22167        a1
22168        ˇb1
22169        [EXCERPT]
22170        [FOLDED]
22171        [EXCERPT]
22172        [FOLDED]
22173        "
22174    });
22175    cx.simulate_keystroke("up");
22176    cx.assert_excerpts_with_selections(indoc! {"
22177        [EXCERPT]
22178        [FOLDED]
22179        [EXCERPT]
22180        ˇa1
22181        b1
22182        [EXCERPT]
22183        [FOLDED]
22184        [EXCERPT]
22185        [FOLDED]
22186        "
22187    });
22188    for _ in 0..5 {
22189        cx.simulate_keystroke("up");
22190        cx.assert_excerpts_with_selections(indoc! {"
22191            [EXCERPT]
22192            ˇ[FOLDED]
22193            [EXCERPT]
22194            a1
22195            b1
22196            [EXCERPT]
22197            [FOLDED]
22198            [EXCERPT]
22199            [FOLDED]
22200            "
22201        });
22202    }
22203}
22204
22205#[gpui::test]
22206async fn test_edit_prediction_text(cx: &mut TestAppContext) {
22207    init_test(cx, |_| {});
22208
22209    // Simple insertion
22210    assert_highlighted_edits(
22211        "Hello, world!",
22212        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
22213        true,
22214        cx,
22215        |highlighted_edits, cx| {
22216            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
22217            assert_eq!(highlighted_edits.highlights.len(), 1);
22218            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
22219            assert_eq!(
22220                highlighted_edits.highlights[0].1.background_color,
22221                Some(cx.theme().status().created_background)
22222            );
22223        },
22224    )
22225    .await;
22226
22227    // Replacement
22228    assert_highlighted_edits(
22229        "This is a test.",
22230        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
22231        false,
22232        cx,
22233        |highlighted_edits, cx| {
22234            assert_eq!(highlighted_edits.text, "That is a test.");
22235            assert_eq!(highlighted_edits.highlights.len(), 1);
22236            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
22237            assert_eq!(
22238                highlighted_edits.highlights[0].1.background_color,
22239                Some(cx.theme().status().created_background)
22240            );
22241        },
22242    )
22243    .await;
22244
22245    // Multiple edits
22246    assert_highlighted_edits(
22247        "Hello, world!",
22248        vec![
22249            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
22250            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
22251        ],
22252        false,
22253        cx,
22254        |highlighted_edits, cx| {
22255            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
22256            assert_eq!(highlighted_edits.highlights.len(), 2);
22257            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
22258            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
22259            assert_eq!(
22260                highlighted_edits.highlights[0].1.background_color,
22261                Some(cx.theme().status().created_background)
22262            );
22263            assert_eq!(
22264                highlighted_edits.highlights[1].1.background_color,
22265                Some(cx.theme().status().created_background)
22266            );
22267        },
22268    )
22269    .await;
22270
22271    // Multiple lines with edits
22272    assert_highlighted_edits(
22273        "First line\nSecond line\nThird line\nFourth line",
22274        vec![
22275            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
22276            (
22277                Point::new(2, 0)..Point::new(2, 10),
22278                "New third line".to_string(),
22279            ),
22280            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
22281        ],
22282        false,
22283        cx,
22284        |highlighted_edits, cx| {
22285            assert_eq!(
22286                highlighted_edits.text,
22287                "Second modified\nNew third line\nFourth updated line"
22288            );
22289            assert_eq!(highlighted_edits.highlights.len(), 3);
22290            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
22291            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
22292            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
22293            for highlight in &highlighted_edits.highlights {
22294                assert_eq!(
22295                    highlight.1.background_color,
22296                    Some(cx.theme().status().created_background)
22297                );
22298            }
22299        },
22300    )
22301    .await;
22302}
22303
22304#[gpui::test]
22305async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
22306    init_test(cx, |_| {});
22307
22308    // Deletion
22309    assert_highlighted_edits(
22310        "Hello, world!",
22311        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
22312        true,
22313        cx,
22314        |highlighted_edits, cx| {
22315            assert_eq!(highlighted_edits.text, "Hello, world!");
22316            assert_eq!(highlighted_edits.highlights.len(), 1);
22317            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
22318            assert_eq!(
22319                highlighted_edits.highlights[0].1.background_color,
22320                Some(cx.theme().status().deleted_background)
22321            );
22322        },
22323    )
22324    .await;
22325
22326    // Insertion
22327    assert_highlighted_edits(
22328        "Hello, world!",
22329        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
22330        true,
22331        cx,
22332        |highlighted_edits, cx| {
22333            assert_eq!(highlighted_edits.highlights.len(), 1);
22334            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
22335            assert_eq!(
22336                highlighted_edits.highlights[0].1.background_color,
22337                Some(cx.theme().status().created_background)
22338            );
22339        },
22340    )
22341    .await;
22342}
22343
22344async fn assert_highlighted_edits(
22345    text: &str,
22346    edits: Vec<(Range<Point>, String)>,
22347    include_deletions: bool,
22348    cx: &mut TestAppContext,
22349    assertion_fn: impl Fn(HighlightedText, &App),
22350) {
22351    let window = cx.add_window(|window, cx| {
22352        let buffer = MultiBuffer::build_simple(text, cx);
22353        Editor::new(EditorMode::full(), buffer, None, window, cx)
22354    });
22355    let cx = &mut VisualTestContext::from_window(*window, cx);
22356
22357    let (buffer, snapshot) = window
22358        .update(cx, |editor, _window, cx| {
22359            (
22360                editor.buffer().clone(),
22361                editor.buffer().read(cx).snapshot(cx),
22362            )
22363        })
22364        .unwrap();
22365
22366    let edits = edits
22367        .into_iter()
22368        .map(|(range, edit)| {
22369            (
22370                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
22371                edit,
22372            )
22373        })
22374        .collect::<Vec<_>>();
22375
22376    let text_anchor_edits = edits
22377        .clone()
22378        .into_iter()
22379        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
22380        .collect::<Vec<_>>();
22381
22382    let edit_preview = window
22383        .update(cx, |_, _window, cx| {
22384            buffer
22385                .read(cx)
22386                .as_singleton()
22387                .unwrap()
22388                .read(cx)
22389                .preview_edits(text_anchor_edits.into(), cx)
22390        })
22391        .unwrap()
22392        .await;
22393
22394    cx.update(|_window, cx| {
22395        let highlighted_edits = edit_prediction_edit_text(
22396            snapshot.as_singleton().unwrap().2,
22397            &edits,
22398            &edit_preview,
22399            include_deletions,
22400            cx,
22401        );
22402        assertion_fn(highlighted_edits, cx)
22403    });
22404}
22405
22406#[track_caller]
22407fn assert_breakpoint(
22408    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
22409    path: &Arc<Path>,
22410    expected: Vec<(u32, Breakpoint)>,
22411) {
22412    if expected.is_empty() {
22413        assert!(!breakpoints.contains_key(path), "{}", path.display());
22414    } else {
22415        let mut breakpoint = breakpoints
22416            .get(path)
22417            .unwrap()
22418            .iter()
22419            .map(|breakpoint| {
22420                (
22421                    breakpoint.row,
22422                    Breakpoint {
22423                        message: breakpoint.message.clone(),
22424                        state: breakpoint.state,
22425                        condition: breakpoint.condition.clone(),
22426                        hit_condition: breakpoint.hit_condition.clone(),
22427                    },
22428                )
22429            })
22430            .collect::<Vec<_>>();
22431
22432        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
22433
22434        assert_eq!(expected, breakpoint);
22435    }
22436}
22437
22438fn add_log_breakpoint_at_cursor(
22439    editor: &mut Editor,
22440    log_message: &str,
22441    window: &mut Window,
22442    cx: &mut Context<Editor>,
22443) {
22444    let (anchor, bp) = editor
22445        .breakpoints_at_cursors(window, cx)
22446        .first()
22447        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
22448        .unwrap_or_else(|| {
22449            let cursor_position: Point = editor.selections.newest(cx).head();
22450
22451            let breakpoint_position = editor
22452                .snapshot(window, cx)
22453                .display_snapshot
22454                .buffer_snapshot
22455                .anchor_before(Point::new(cursor_position.row, 0));
22456
22457            (breakpoint_position, Breakpoint::new_log(log_message))
22458        });
22459
22460    editor.edit_breakpoint_at_anchor(
22461        anchor,
22462        bp,
22463        BreakpointEditAction::EditLogMessage(log_message.into()),
22464        cx,
22465    );
22466}
22467
22468#[gpui::test]
22469async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
22470    init_test(cx, |_| {});
22471
22472    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22473    let fs = FakeFs::new(cx.executor());
22474    fs.insert_tree(
22475        path!("/a"),
22476        json!({
22477            "main.rs": sample_text,
22478        }),
22479    )
22480    .await;
22481    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22482    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22483    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22484
22485    let fs = FakeFs::new(cx.executor());
22486    fs.insert_tree(
22487        path!("/a"),
22488        json!({
22489            "main.rs": sample_text,
22490        }),
22491    )
22492    .await;
22493    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22494    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22495    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22496    let worktree_id = workspace
22497        .update(cx, |workspace, _window, cx| {
22498            workspace.project().update(cx, |project, cx| {
22499                project.worktrees(cx).next().unwrap().read(cx).id()
22500            })
22501        })
22502        .unwrap();
22503
22504    let buffer = project
22505        .update(cx, |project, cx| {
22506            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22507        })
22508        .await
22509        .unwrap();
22510
22511    let (editor, cx) = cx.add_window_view(|window, cx| {
22512        Editor::new(
22513            EditorMode::full(),
22514            MultiBuffer::build_from_buffer(buffer, cx),
22515            Some(project.clone()),
22516            window,
22517            cx,
22518        )
22519    });
22520
22521    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22522    let abs_path = project.read_with(cx, |project, cx| {
22523        project
22524            .absolute_path(&project_path, cx)
22525            .map(Arc::from)
22526            .unwrap()
22527    });
22528
22529    // assert we can add breakpoint on the first line
22530    editor.update_in(cx, |editor, window, cx| {
22531        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22532        editor.move_to_end(&MoveToEnd, window, cx);
22533        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22534    });
22535
22536    let breakpoints = editor.update(cx, |editor, cx| {
22537        editor
22538            .breakpoint_store()
22539            .as_ref()
22540            .unwrap()
22541            .read(cx)
22542            .all_source_breakpoints(cx)
22543    });
22544
22545    assert_eq!(1, breakpoints.len());
22546    assert_breakpoint(
22547        &breakpoints,
22548        &abs_path,
22549        vec![
22550            (0, Breakpoint::new_standard()),
22551            (3, Breakpoint::new_standard()),
22552        ],
22553    );
22554
22555    editor.update_in(cx, |editor, window, cx| {
22556        editor.move_to_beginning(&MoveToBeginning, window, cx);
22557        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22558    });
22559
22560    let breakpoints = editor.update(cx, |editor, cx| {
22561        editor
22562            .breakpoint_store()
22563            .as_ref()
22564            .unwrap()
22565            .read(cx)
22566            .all_source_breakpoints(cx)
22567    });
22568
22569    assert_eq!(1, breakpoints.len());
22570    assert_breakpoint(
22571        &breakpoints,
22572        &abs_path,
22573        vec![(3, Breakpoint::new_standard())],
22574    );
22575
22576    editor.update_in(cx, |editor, window, cx| {
22577        editor.move_to_end(&MoveToEnd, window, cx);
22578        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22579    });
22580
22581    let breakpoints = editor.update(cx, |editor, cx| {
22582        editor
22583            .breakpoint_store()
22584            .as_ref()
22585            .unwrap()
22586            .read(cx)
22587            .all_source_breakpoints(cx)
22588    });
22589
22590    assert_eq!(0, breakpoints.len());
22591    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22592}
22593
22594#[gpui::test]
22595async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
22596    init_test(cx, |_| {});
22597
22598    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22599
22600    let fs = FakeFs::new(cx.executor());
22601    fs.insert_tree(
22602        path!("/a"),
22603        json!({
22604            "main.rs": sample_text,
22605        }),
22606    )
22607    .await;
22608    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22609    let (workspace, cx) =
22610        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22611
22612    let worktree_id = workspace.update(cx, |workspace, cx| {
22613        workspace.project().update(cx, |project, cx| {
22614            project.worktrees(cx).next().unwrap().read(cx).id()
22615        })
22616    });
22617
22618    let buffer = project
22619        .update(cx, |project, cx| {
22620            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22621        })
22622        .await
22623        .unwrap();
22624
22625    let (editor, cx) = cx.add_window_view(|window, cx| {
22626        Editor::new(
22627            EditorMode::full(),
22628            MultiBuffer::build_from_buffer(buffer, cx),
22629            Some(project.clone()),
22630            window,
22631            cx,
22632        )
22633    });
22634
22635    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22636    let abs_path = project.read_with(cx, |project, cx| {
22637        project
22638            .absolute_path(&project_path, cx)
22639            .map(Arc::from)
22640            .unwrap()
22641    });
22642
22643    editor.update_in(cx, |editor, window, cx| {
22644        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22645    });
22646
22647    let breakpoints = editor.update(cx, |editor, cx| {
22648        editor
22649            .breakpoint_store()
22650            .as_ref()
22651            .unwrap()
22652            .read(cx)
22653            .all_source_breakpoints(cx)
22654    });
22655
22656    assert_breakpoint(
22657        &breakpoints,
22658        &abs_path,
22659        vec![(0, Breakpoint::new_log("hello world"))],
22660    );
22661
22662    // Removing a log message from a log breakpoint should remove it
22663    editor.update_in(cx, |editor, window, cx| {
22664        add_log_breakpoint_at_cursor(editor, "", window, cx);
22665    });
22666
22667    let breakpoints = editor.update(cx, |editor, cx| {
22668        editor
22669            .breakpoint_store()
22670            .as_ref()
22671            .unwrap()
22672            .read(cx)
22673            .all_source_breakpoints(cx)
22674    });
22675
22676    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22677
22678    editor.update_in(cx, |editor, window, cx| {
22679        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22680        editor.move_to_end(&MoveToEnd, window, cx);
22681        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22682        // Not adding a log message to a standard breakpoint shouldn't remove it
22683        add_log_breakpoint_at_cursor(editor, "", window, cx);
22684    });
22685
22686    let breakpoints = editor.update(cx, |editor, cx| {
22687        editor
22688            .breakpoint_store()
22689            .as_ref()
22690            .unwrap()
22691            .read(cx)
22692            .all_source_breakpoints(cx)
22693    });
22694
22695    assert_breakpoint(
22696        &breakpoints,
22697        &abs_path,
22698        vec![
22699            (0, Breakpoint::new_standard()),
22700            (3, Breakpoint::new_standard()),
22701        ],
22702    );
22703
22704    editor.update_in(cx, |editor, window, cx| {
22705        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22706    });
22707
22708    let breakpoints = editor.update(cx, |editor, cx| {
22709        editor
22710            .breakpoint_store()
22711            .as_ref()
22712            .unwrap()
22713            .read(cx)
22714            .all_source_breakpoints(cx)
22715    });
22716
22717    assert_breakpoint(
22718        &breakpoints,
22719        &abs_path,
22720        vec![
22721            (0, Breakpoint::new_standard()),
22722            (3, Breakpoint::new_log("hello world")),
22723        ],
22724    );
22725
22726    editor.update_in(cx, |editor, window, cx| {
22727        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
22728    });
22729
22730    let breakpoints = editor.update(cx, |editor, cx| {
22731        editor
22732            .breakpoint_store()
22733            .as_ref()
22734            .unwrap()
22735            .read(cx)
22736            .all_source_breakpoints(cx)
22737    });
22738
22739    assert_breakpoint(
22740        &breakpoints,
22741        &abs_path,
22742        vec![
22743            (0, Breakpoint::new_standard()),
22744            (3, Breakpoint::new_log("hello Earth!!")),
22745        ],
22746    );
22747}
22748
22749/// This also tests that Editor::breakpoint_at_cursor_head is working properly
22750/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
22751/// or when breakpoints were placed out of order. This tests for a regression too
22752#[gpui::test]
22753async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
22754    init_test(cx, |_| {});
22755
22756    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22757    let fs = FakeFs::new(cx.executor());
22758    fs.insert_tree(
22759        path!("/a"),
22760        json!({
22761            "main.rs": sample_text,
22762        }),
22763    )
22764    .await;
22765    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22766    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22767    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22768
22769    let fs = FakeFs::new(cx.executor());
22770    fs.insert_tree(
22771        path!("/a"),
22772        json!({
22773            "main.rs": sample_text,
22774        }),
22775    )
22776    .await;
22777    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22778    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22779    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22780    let worktree_id = workspace
22781        .update(cx, |workspace, _window, cx| {
22782            workspace.project().update(cx, |project, cx| {
22783                project.worktrees(cx).next().unwrap().read(cx).id()
22784            })
22785        })
22786        .unwrap();
22787
22788    let buffer = project
22789        .update(cx, |project, cx| {
22790            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22791        })
22792        .await
22793        .unwrap();
22794
22795    let (editor, cx) = cx.add_window_view(|window, cx| {
22796        Editor::new(
22797            EditorMode::full(),
22798            MultiBuffer::build_from_buffer(buffer, cx),
22799            Some(project.clone()),
22800            window,
22801            cx,
22802        )
22803    });
22804
22805    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22806    let abs_path = project.read_with(cx, |project, cx| {
22807        project
22808            .absolute_path(&project_path, cx)
22809            .map(Arc::from)
22810            .unwrap()
22811    });
22812
22813    // assert we can add breakpoint on the first line
22814    editor.update_in(cx, |editor, window, cx| {
22815        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22816        editor.move_to_end(&MoveToEnd, window, cx);
22817        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22818        editor.move_up(&MoveUp, window, cx);
22819        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22820    });
22821
22822    let breakpoints = editor.update(cx, |editor, cx| {
22823        editor
22824            .breakpoint_store()
22825            .as_ref()
22826            .unwrap()
22827            .read(cx)
22828            .all_source_breakpoints(cx)
22829    });
22830
22831    assert_eq!(1, breakpoints.len());
22832    assert_breakpoint(
22833        &breakpoints,
22834        &abs_path,
22835        vec![
22836            (0, Breakpoint::new_standard()),
22837            (2, Breakpoint::new_standard()),
22838            (3, Breakpoint::new_standard()),
22839        ],
22840    );
22841
22842    editor.update_in(cx, |editor, window, cx| {
22843        editor.move_to_beginning(&MoveToBeginning, window, cx);
22844        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22845        editor.move_to_end(&MoveToEnd, window, cx);
22846        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22847        // Disabling a breakpoint that doesn't exist should do nothing
22848        editor.move_up(&MoveUp, window, cx);
22849        editor.move_up(&MoveUp, window, cx);
22850        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22851    });
22852
22853    let breakpoints = editor.update(cx, |editor, cx| {
22854        editor
22855            .breakpoint_store()
22856            .as_ref()
22857            .unwrap()
22858            .read(cx)
22859            .all_source_breakpoints(cx)
22860    });
22861
22862    let disable_breakpoint = {
22863        let mut bp = Breakpoint::new_standard();
22864        bp.state = BreakpointState::Disabled;
22865        bp
22866    };
22867
22868    assert_eq!(1, breakpoints.len());
22869    assert_breakpoint(
22870        &breakpoints,
22871        &abs_path,
22872        vec![
22873            (0, disable_breakpoint.clone()),
22874            (2, Breakpoint::new_standard()),
22875            (3, disable_breakpoint.clone()),
22876        ],
22877    );
22878
22879    editor.update_in(cx, |editor, window, cx| {
22880        editor.move_to_beginning(&MoveToBeginning, window, cx);
22881        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22882        editor.move_to_end(&MoveToEnd, window, cx);
22883        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22884        editor.move_up(&MoveUp, window, cx);
22885        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22886    });
22887
22888    let breakpoints = editor.update(cx, |editor, cx| {
22889        editor
22890            .breakpoint_store()
22891            .as_ref()
22892            .unwrap()
22893            .read(cx)
22894            .all_source_breakpoints(cx)
22895    });
22896
22897    assert_eq!(1, breakpoints.len());
22898    assert_breakpoint(
22899        &breakpoints,
22900        &abs_path,
22901        vec![
22902            (0, Breakpoint::new_standard()),
22903            (2, disable_breakpoint),
22904            (3, Breakpoint::new_standard()),
22905        ],
22906    );
22907}
22908
22909#[gpui::test]
22910async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
22911    init_test(cx, |_| {});
22912    let capabilities = lsp::ServerCapabilities {
22913        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
22914            prepare_provider: Some(true),
22915            work_done_progress_options: Default::default(),
22916        })),
22917        ..Default::default()
22918    };
22919    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
22920
22921    cx.set_state(indoc! {"
22922        struct Fˇoo {}
22923    "});
22924
22925    cx.update_editor(|editor, _, cx| {
22926        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
22927        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
22928        editor.highlight_background::<DocumentHighlightRead>(
22929            &[highlight_range],
22930            |theme| theme.colors().editor_document_highlight_read_background,
22931            cx,
22932        );
22933    });
22934
22935    let mut prepare_rename_handler = cx
22936        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
22937            move |_, _, _| async move {
22938                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
22939                    start: lsp::Position {
22940                        line: 0,
22941                        character: 7,
22942                    },
22943                    end: lsp::Position {
22944                        line: 0,
22945                        character: 10,
22946                    },
22947                })))
22948            },
22949        );
22950    let prepare_rename_task = cx
22951        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
22952        .expect("Prepare rename was not started");
22953    prepare_rename_handler.next().await.unwrap();
22954    prepare_rename_task.await.expect("Prepare rename failed");
22955
22956    let mut rename_handler =
22957        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
22958            let edit = lsp::TextEdit {
22959                range: lsp::Range {
22960                    start: lsp::Position {
22961                        line: 0,
22962                        character: 7,
22963                    },
22964                    end: lsp::Position {
22965                        line: 0,
22966                        character: 10,
22967                    },
22968                },
22969                new_text: "FooRenamed".to_string(),
22970            };
22971            Ok(Some(lsp::WorkspaceEdit::new(
22972                // Specify the same edit twice
22973                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
22974            )))
22975        });
22976    let rename_task = cx
22977        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
22978        .expect("Confirm rename was not started");
22979    rename_handler.next().await.unwrap();
22980    rename_task.await.expect("Confirm rename failed");
22981    cx.run_until_parked();
22982
22983    // Despite two edits, only one is actually applied as those are identical
22984    cx.assert_editor_state(indoc! {"
22985        struct FooRenamedˇ {}
22986    "});
22987}
22988
22989#[gpui::test]
22990async fn test_rename_without_prepare(cx: &mut TestAppContext) {
22991    init_test(cx, |_| {});
22992    // These capabilities indicate that the server does not support prepare rename.
22993    let capabilities = lsp::ServerCapabilities {
22994        rename_provider: Some(lsp::OneOf::Left(true)),
22995        ..Default::default()
22996    };
22997    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
22998
22999    cx.set_state(indoc! {"
23000        struct Fˇoo {}
23001    "});
23002
23003    cx.update_editor(|editor, _window, cx| {
23004        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23005        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23006        editor.highlight_background::<DocumentHighlightRead>(
23007            &[highlight_range],
23008            |theme| theme.colors().editor_document_highlight_read_background,
23009            cx,
23010        );
23011    });
23012
23013    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23014        .expect("Prepare rename was not started")
23015        .await
23016        .expect("Prepare rename failed");
23017
23018    let mut rename_handler =
23019        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23020            let edit = lsp::TextEdit {
23021                range: lsp::Range {
23022                    start: lsp::Position {
23023                        line: 0,
23024                        character: 7,
23025                    },
23026                    end: lsp::Position {
23027                        line: 0,
23028                        character: 10,
23029                    },
23030                },
23031                new_text: "FooRenamed".to_string(),
23032            };
23033            Ok(Some(lsp::WorkspaceEdit::new(
23034                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23035            )))
23036        });
23037    let rename_task = cx
23038        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23039        .expect("Confirm rename was not started");
23040    rename_handler.next().await.unwrap();
23041    rename_task.await.expect("Confirm rename failed");
23042    cx.run_until_parked();
23043
23044    // Correct range is renamed, as `surrounding_word` is used to find it.
23045    cx.assert_editor_state(indoc! {"
23046        struct FooRenamedˇ {}
23047    "});
23048}
23049
23050#[gpui::test]
23051async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23052    init_test(cx, |_| {});
23053    let mut cx = EditorTestContext::new(cx).await;
23054
23055    let language = Arc::new(
23056        Language::new(
23057            LanguageConfig::default(),
23058            Some(tree_sitter_html::LANGUAGE.into()),
23059        )
23060        .with_brackets_query(
23061            r#"
23062            ("<" @open "/>" @close)
23063            ("</" @open ">" @close)
23064            ("<" @open ">" @close)
23065            ("\"" @open "\"" @close)
23066            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23067        "#,
23068        )
23069        .unwrap(),
23070    );
23071    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23072
23073    cx.set_state(indoc! {"
23074        <span>ˇ</span>
23075    "});
23076    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23077    cx.assert_editor_state(indoc! {"
23078        <span>
23079        ˇ
23080        </span>
23081    "});
23082
23083    cx.set_state(indoc! {"
23084        <span><span></span>ˇ</span>
23085    "});
23086    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23087    cx.assert_editor_state(indoc! {"
23088        <span><span></span>
23089        ˇ</span>
23090    "});
23091
23092    cx.set_state(indoc! {"
23093        <span>ˇ
23094        </span>
23095    "});
23096    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23097    cx.assert_editor_state(indoc! {"
23098        <span>
23099        ˇ
23100        </span>
23101    "});
23102}
23103
23104#[gpui::test(iterations = 10)]
23105async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23106    init_test(cx, |_| {});
23107
23108    let fs = FakeFs::new(cx.executor());
23109    fs.insert_tree(
23110        path!("/dir"),
23111        json!({
23112            "a.ts": "a",
23113        }),
23114    )
23115    .await;
23116
23117    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23118    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23119    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23120
23121    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23122    language_registry.add(Arc::new(Language::new(
23123        LanguageConfig {
23124            name: "TypeScript".into(),
23125            matcher: LanguageMatcher {
23126                path_suffixes: vec!["ts".to_string()],
23127                ..Default::default()
23128            },
23129            ..Default::default()
23130        },
23131        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
23132    )));
23133    let mut fake_language_servers = language_registry.register_fake_lsp(
23134        "TypeScript",
23135        FakeLspAdapter {
23136            capabilities: lsp::ServerCapabilities {
23137                code_lens_provider: Some(lsp::CodeLensOptions {
23138                    resolve_provider: Some(true),
23139                }),
23140                execute_command_provider: Some(lsp::ExecuteCommandOptions {
23141                    commands: vec!["_the/command".to_string()],
23142                    ..lsp::ExecuteCommandOptions::default()
23143                }),
23144                ..lsp::ServerCapabilities::default()
23145            },
23146            ..FakeLspAdapter::default()
23147        },
23148    );
23149
23150    let editor = workspace
23151        .update(cx, |workspace, window, cx| {
23152            workspace.open_abs_path(
23153                PathBuf::from(path!("/dir/a.ts")),
23154                OpenOptions::default(),
23155                window,
23156                cx,
23157            )
23158        })
23159        .unwrap()
23160        .await
23161        .unwrap()
23162        .downcast::<Editor>()
23163        .unwrap();
23164    cx.executor().run_until_parked();
23165
23166    let fake_server = fake_language_servers.next().await.unwrap();
23167
23168    let buffer = editor.update(cx, |editor, cx| {
23169        editor
23170            .buffer()
23171            .read(cx)
23172            .as_singleton()
23173            .expect("have opened a single file by path")
23174    });
23175
23176    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
23177    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
23178    drop(buffer_snapshot);
23179    let actions = cx
23180        .update_window(*workspace, |_, window, cx| {
23181            project.code_actions(&buffer, anchor..anchor, window, cx)
23182        })
23183        .unwrap();
23184
23185    fake_server
23186        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23187            Ok(Some(vec![
23188                lsp::CodeLens {
23189                    range: lsp::Range::default(),
23190                    command: Some(lsp::Command {
23191                        title: "Code lens command".to_owned(),
23192                        command: "_the/command".to_owned(),
23193                        arguments: None,
23194                    }),
23195                    data: None,
23196                },
23197                lsp::CodeLens {
23198                    range: lsp::Range::default(),
23199                    command: Some(lsp::Command {
23200                        title: "Command not in capabilities".to_owned(),
23201                        command: "not in capabilities".to_owned(),
23202                        arguments: None,
23203                    }),
23204                    data: None,
23205                },
23206                lsp::CodeLens {
23207                    range: lsp::Range {
23208                        start: lsp::Position {
23209                            line: 1,
23210                            character: 1,
23211                        },
23212                        end: lsp::Position {
23213                            line: 1,
23214                            character: 1,
23215                        },
23216                    },
23217                    command: Some(lsp::Command {
23218                        title: "Command not in range".to_owned(),
23219                        command: "_the/command".to_owned(),
23220                        arguments: None,
23221                    }),
23222                    data: None,
23223                },
23224            ]))
23225        })
23226        .next()
23227        .await;
23228
23229    let actions = actions.await.unwrap();
23230    assert_eq!(
23231        actions.len(),
23232        1,
23233        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
23234    );
23235    let action = actions[0].clone();
23236    let apply = project.update(cx, |project, cx| {
23237        project.apply_code_action(buffer.clone(), action, true, cx)
23238    });
23239
23240    // Resolving the code action does not populate its edits. In absence of
23241    // edits, we must execute the given command.
23242    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
23243        |mut lens, _| async move {
23244            let lens_command = lens.command.as_mut().expect("should have a command");
23245            assert_eq!(lens_command.title, "Code lens command");
23246            lens_command.arguments = Some(vec![json!("the-argument")]);
23247            Ok(lens)
23248        },
23249    );
23250
23251    // While executing the command, the language server sends the editor
23252    // a `workspaceEdit` request.
23253    fake_server
23254        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
23255            let fake = fake_server.clone();
23256            move |params, _| {
23257                assert_eq!(params.command, "_the/command");
23258                let fake = fake.clone();
23259                async move {
23260                    fake.server
23261                        .request::<lsp::request::ApplyWorkspaceEdit>(
23262                            lsp::ApplyWorkspaceEditParams {
23263                                label: None,
23264                                edit: lsp::WorkspaceEdit {
23265                                    changes: Some(
23266                                        [(
23267                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
23268                                            vec![lsp::TextEdit {
23269                                                range: lsp::Range::new(
23270                                                    lsp::Position::new(0, 0),
23271                                                    lsp::Position::new(0, 0),
23272                                                ),
23273                                                new_text: "X".into(),
23274                                            }],
23275                                        )]
23276                                        .into_iter()
23277                                        .collect(),
23278                                    ),
23279                                    ..lsp::WorkspaceEdit::default()
23280                                },
23281                            },
23282                        )
23283                        .await
23284                        .into_response()
23285                        .unwrap();
23286                    Ok(Some(json!(null)))
23287                }
23288            }
23289        })
23290        .next()
23291        .await;
23292
23293    // Applying the code lens command returns a project transaction containing the edits
23294    // sent by the language server in its `workspaceEdit` request.
23295    let transaction = apply.await.unwrap();
23296    assert!(transaction.0.contains_key(&buffer));
23297    buffer.update(cx, |buffer, cx| {
23298        assert_eq!(buffer.text(), "Xa");
23299        buffer.undo(cx);
23300        assert_eq!(buffer.text(), "a");
23301    });
23302
23303    let actions_after_edits = cx
23304        .update_window(*workspace, |_, window, cx| {
23305            project.code_actions(&buffer, anchor..anchor, window, cx)
23306        })
23307        .unwrap()
23308        .await
23309        .unwrap();
23310    assert_eq!(
23311        actions, actions_after_edits,
23312        "For the same selection, same code lens actions should be returned"
23313    );
23314
23315    let _responses =
23316        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23317            panic!("No more code lens requests are expected");
23318        });
23319    editor.update_in(cx, |editor, window, cx| {
23320        editor.select_all(&SelectAll, window, cx);
23321    });
23322    cx.executor().run_until_parked();
23323    let new_actions = cx
23324        .update_window(*workspace, |_, window, cx| {
23325            project.code_actions(&buffer, anchor..anchor, window, cx)
23326        })
23327        .unwrap()
23328        .await
23329        .unwrap();
23330    assert_eq!(
23331        actions, new_actions,
23332        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
23333    );
23334}
23335
23336#[gpui::test]
23337async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
23338    init_test(cx, |_| {});
23339
23340    let fs = FakeFs::new(cx.executor());
23341    let main_text = r#"fn main() {
23342println!("1");
23343println!("2");
23344println!("3");
23345println!("4");
23346println!("5");
23347}"#;
23348    let lib_text = "mod foo {}";
23349    fs.insert_tree(
23350        path!("/a"),
23351        json!({
23352            "lib.rs": lib_text,
23353            "main.rs": main_text,
23354        }),
23355    )
23356    .await;
23357
23358    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23359    let (workspace, cx) =
23360        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23361    let worktree_id = workspace.update(cx, |workspace, cx| {
23362        workspace.project().update(cx, |project, cx| {
23363            project.worktrees(cx).next().unwrap().read(cx).id()
23364        })
23365    });
23366
23367    let expected_ranges = vec![
23368        Point::new(0, 0)..Point::new(0, 0),
23369        Point::new(1, 0)..Point::new(1, 1),
23370        Point::new(2, 0)..Point::new(2, 2),
23371        Point::new(3, 0)..Point::new(3, 3),
23372    ];
23373
23374    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23375    let editor_1 = workspace
23376        .update_in(cx, |workspace, window, cx| {
23377            workspace.open_path(
23378                (worktree_id, rel_path("main.rs")),
23379                Some(pane_1.downgrade()),
23380                true,
23381                window,
23382                cx,
23383            )
23384        })
23385        .unwrap()
23386        .await
23387        .downcast::<Editor>()
23388        .unwrap();
23389    pane_1.update(cx, |pane, cx| {
23390        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23391        open_editor.update(cx, |editor, cx| {
23392            assert_eq!(
23393                editor.display_text(cx),
23394                main_text,
23395                "Original main.rs text on initial open",
23396            );
23397            assert_eq!(
23398                editor
23399                    .selections
23400                    .all::<Point>(cx)
23401                    .into_iter()
23402                    .map(|s| s.range())
23403                    .collect::<Vec<_>>(),
23404                vec![Point::zero()..Point::zero()],
23405                "Default selections on initial open",
23406            );
23407        })
23408    });
23409    editor_1.update_in(cx, |editor, window, cx| {
23410        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23411            s.select_ranges(expected_ranges.clone());
23412        });
23413    });
23414
23415    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
23416        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
23417    });
23418    let editor_2 = workspace
23419        .update_in(cx, |workspace, window, cx| {
23420            workspace.open_path(
23421                (worktree_id, rel_path("main.rs")),
23422                Some(pane_2.downgrade()),
23423                true,
23424                window,
23425                cx,
23426            )
23427        })
23428        .unwrap()
23429        .await
23430        .downcast::<Editor>()
23431        .unwrap();
23432    pane_2.update(cx, |pane, cx| {
23433        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23434        open_editor.update(cx, |editor, cx| {
23435            assert_eq!(
23436                editor.display_text(cx),
23437                main_text,
23438                "Original main.rs text on initial open in another panel",
23439            );
23440            assert_eq!(
23441                editor
23442                    .selections
23443                    .all::<Point>(cx)
23444                    .into_iter()
23445                    .map(|s| s.range())
23446                    .collect::<Vec<_>>(),
23447                vec![Point::zero()..Point::zero()],
23448                "Default selections on initial open in another panel",
23449            );
23450        })
23451    });
23452
23453    editor_2.update_in(cx, |editor, window, cx| {
23454        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23455    });
23456
23457    let _other_editor_1 = workspace
23458        .update_in(cx, |workspace, window, cx| {
23459            workspace.open_path(
23460                (worktree_id, rel_path("lib.rs")),
23461                Some(pane_1.downgrade()),
23462                true,
23463                window,
23464                cx,
23465            )
23466        })
23467        .unwrap()
23468        .await
23469        .downcast::<Editor>()
23470        .unwrap();
23471    pane_1
23472        .update_in(cx, |pane, window, cx| {
23473            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23474        })
23475        .await
23476        .unwrap();
23477    drop(editor_1);
23478    pane_1.update(cx, |pane, cx| {
23479        pane.active_item()
23480            .unwrap()
23481            .downcast::<Editor>()
23482            .unwrap()
23483            .update(cx, |editor, cx| {
23484                assert_eq!(
23485                    editor.display_text(cx),
23486                    lib_text,
23487                    "Other file should be open and active",
23488                );
23489            });
23490        assert_eq!(pane.items().count(), 1, "No other editors should be open");
23491    });
23492
23493    let _other_editor_2 = workspace
23494        .update_in(cx, |workspace, window, cx| {
23495            workspace.open_path(
23496                (worktree_id, rel_path("lib.rs")),
23497                Some(pane_2.downgrade()),
23498                true,
23499                window,
23500                cx,
23501            )
23502        })
23503        .unwrap()
23504        .await
23505        .downcast::<Editor>()
23506        .unwrap();
23507    pane_2
23508        .update_in(cx, |pane, window, cx| {
23509            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23510        })
23511        .await
23512        .unwrap();
23513    drop(editor_2);
23514    pane_2.update(cx, |pane, cx| {
23515        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23516        open_editor.update(cx, |editor, cx| {
23517            assert_eq!(
23518                editor.display_text(cx),
23519                lib_text,
23520                "Other file should be open and active in another panel too",
23521            );
23522        });
23523        assert_eq!(
23524            pane.items().count(),
23525            1,
23526            "No other editors should be open in another pane",
23527        );
23528    });
23529
23530    let _editor_1_reopened = workspace
23531        .update_in(cx, |workspace, window, cx| {
23532            workspace.open_path(
23533                (worktree_id, rel_path("main.rs")),
23534                Some(pane_1.downgrade()),
23535                true,
23536                window,
23537                cx,
23538            )
23539        })
23540        .unwrap()
23541        .await
23542        .downcast::<Editor>()
23543        .unwrap();
23544    let _editor_2_reopened = workspace
23545        .update_in(cx, |workspace, window, cx| {
23546            workspace.open_path(
23547                (worktree_id, rel_path("main.rs")),
23548                Some(pane_2.downgrade()),
23549                true,
23550                window,
23551                cx,
23552            )
23553        })
23554        .unwrap()
23555        .await
23556        .downcast::<Editor>()
23557        .unwrap();
23558    pane_1.update(cx, |pane, cx| {
23559        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23560        open_editor.update(cx, |editor, cx| {
23561            assert_eq!(
23562                editor.display_text(cx),
23563                main_text,
23564                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
23565            );
23566            assert_eq!(
23567                editor
23568                    .selections
23569                    .all::<Point>(cx)
23570                    .into_iter()
23571                    .map(|s| s.range())
23572                    .collect::<Vec<_>>(),
23573                expected_ranges,
23574                "Previous editor in the 1st panel had selections and should get them restored on reopen",
23575            );
23576        })
23577    });
23578    pane_2.update(cx, |pane, cx| {
23579        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23580        open_editor.update(cx, |editor, cx| {
23581            assert_eq!(
23582                editor.display_text(cx),
23583                r#"fn main() {
23584⋯rintln!("1");
23585⋯intln!("2");
23586⋯ntln!("3");
23587println!("4");
23588println!("5");
23589}"#,
23590                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
23591            );
23592            assert_eq!(
23593                editor
23594                    .selections
23595                    .all::<Point>(cx)
23596                    .into_iter()
23597                    .map(|s| s.range())
23598                    .collect::<Vec<_>>(),
23599                vec![Point::zero()..Point::zero()],
23600                "Previous editor in the 2nd pane had no selections changed hence should restore none",
23601            );
23602        })
23603    });
23604}
23605
23606#[gpui::test]
23607async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
23608    init_test(cx, |_| {});
23609
23610    let fs = FakeFs::new(cx.executor());
23611    let main_text = r#"fn main() {
23612println!("1");
23613println!("2");
23614println!("3");
23615println!("4");
23616println!("5");
23617}"#;
23618    let lib_text = "mod foo {}";
23619    fs.insert_tree(
23620        path!("/a"),
23621        json!({
23622            "lib.rs": lib_text,
23623            "main.rs": main_text,
23624        }),
23625    )
23626    .await;
23627
23628    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23629    let (workspace, cx) =
23630        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23631    let worktree_id = workspace.update(cx, |workspace, cx| {
23632        workspace.project().update(cx, |project, cx| {
23633            project.worktrees(cx).next().unwrap().read(cx).id()
23634        })
23635    });
23636
23637    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23638    let editor = workspace
23639        .update_in(cx, |workspace, window, cx| {
23640            workspace.open_path(
23641                (worktree_id, rel_path("main.rs")),
23642                Some(pane.downgrade()),
23643                true,
23644                window,
23645                cx,
23646            )
23647        })
23648        .unwrap()
23649        .await
23650        .downcast::<Editor>()
23651        .unwrap();
23652    pane.update(cx, |pane, cx| {
23653        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23654        open_editor.update(cx, |editor, cx| {
23655            assert_eq!(
23656                editor.display_text(cx),
23657                main_text,
23658                "Original main.rs text on initial open",
23659            );
23660        })
23661    });
23662    editor.update_in(cx, |editor, window, cx| {
23663        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
23664    });
23665
23666    cx.update_global(|store: &mut SettingsStore, cx| {
23667        store.update_user_settings(cx, |s| {
23668            s.workspace.restore_on_file_reopen = Some(false);
23669        });
23670    });
23671    editor.update_in(cx, |editor, window, cx| {
23672        editor.fold_ranges(
23673            vec![
23674                Point::new(1, 0)..Point::new(1, 1),
23675                Point::new(2, 0)..Point::new(2, 2),
23676                Point::new(3, 0)..Point::new(3, 3),
23677            ],
23678            false,
23679            window,
23680            cx,
23681        );
23682    });
23683    pane.update_in(cx, |pane, window, cx| {
23684        pane.close_all_items(&CloseAllItems::default(), window, cx)
23685    })
23686    .await
23687    .unwrap();
23688    pane.update(cx, |pane, _| {
23689        assert!(pane.active_item().is_none());
23690    });
23691    cx.update_global(|store: &mut SettingsStore, cx| {
23692        store.update_user_settings(cx, |s| {
23693            s.workspace.restore_on_file_reopen = Some(true);
23694        });
23695    });
23696
23697    let _editor_reopened = workspace
23698        .update_in(cx, |workspace, window, cx| {
23699            workspace.open_path(
23700                (worktree_id, rel_path("main.rs")),
23701                Some(pane.downgrade()),
23702                true,
23703                window,
23704                cx,
23705            )
23706        })
23707        .unwrap()
23708        .await
23709        .downcast::<Editor>()
23710        .unwrap();
23711    pane.update(cx, |pane, cx| {
23712        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23713        open_editor.update(cx, |editor, cx| {
23714            assert_eq!(
23715                editor.display_text(cx),
23716                main_text,
23717                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
23718            );
23719        })
23720    });
23721}
23722
23723#[gpui::test]
23724async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
23725    struct EmptyModalView {
23726        focus_handle: gpui::FocusHandle,
23727    }
23728    impl EventEmitter<DismissEvent> for EmptyModalView {}
23729    impl Render for EmptyModalView {
23730        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
23731            div()
23732        }
23733    }
23734    impl Focusable for EmptyModalView {
23735        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
23736            self.focus_handle.clone()
23737        }
23738    }
23739    impl workspace::ModalView for EmptyModalView {}
23740    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
23741        EmptyModalView {
23742            focus_handle: cx.focus_handle(),
23743        }
23744    }
23745
23746    init_test(cx, |_| {});
23747
23748    let fs = FakeFs::new(cx.executor());
23749    let project = Project::test(fs, [], cx).await;
23750    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23751    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
23752    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23753    let editor = cx.new_window_entity(|window, cx| {
23754        Editor::new(
23755            EditorMode::full(),
23756            buffer,
23757            Some(project.clone()),
23758            window,
23759            cx,
23760        )
23761    });
23762    workspace
23763        .update(cx, |workspace, window, cx| {
23764            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
23765        })
23766        .unwrap();
23767    editor.update_in(cx, |editor, window, cx| {
23768        editor.open_context_menu(&OpenContextMenu, window, cx);
23769        assert!(editor.mouse_context_menu.is_some());
23770    });
23771    workspace
23772        .update(cx, |workspace, window, cx| {
23773            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
23774        })
23775        .unwrap();
23776    cx.read(|cx| {
23777        assert!(editor.read(cx).mouse_context_menu.is_none());
23778    });
23779}
23780
23781fn set_linked_edit_ranges(
23782    opening: (Point, Point),
23783    closing: (Point, Point),
23784    editor: &mut Editor,
23785    cx: &mut Context<Editor>,
23786) {
23787    let Some((buffer, _)) = editor
23788        .buffer
23789        .read(cx)
23790        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
23791    else {
23792        panic!("Failed to get buffer for selection position");
23793    };
23794    let buffer = buffer.read(cx);
23795    let buffer_id = buffer.remote_id();
23796    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
23797    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
23798    let mut linked_ranges = HashMap::default();
23799    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
23800    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
23801}
23802
23803#[gpui::test]
23804async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
23805    init_test(cx, |_| {});
23806
23807    let fs = FakeFs::new(cx.executor());
23808    fs.insert_file(path!("/file.html"), Default::default())
23809        .await;
23810
23811    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
23812
23813    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23814    let html_language = Arc::new(Language::new(
23815        LanguageConfig {
23816            name: "HTML".into(),
23817            matcher: LanguageMatcher {
23818                path_suffixes: vec!["html".to_string()],
23819                ..LanguageMatcher::default()
23820            },
23821            brackets: BracketPairConfig {
23822                pairs: vec![BracketPair {
23823                    start: "<".into(),
23824                    end: ">".into(),
23825                    close: true,
23826                    ..Default::default()
23827                }],
23828                ..Default::default()
23829            },
23830            ..Default::default()
23831        },
23832        Some(tree_sitter_html::LANGUAGE.into()),
23833    ));
23834    language_registry.add(html_language);
23835    let mut fake_servers = language_registry.register_fake_lsp(
23836        "HTML",
23837        FakeLspAdapter {
23838            capabilities: lsp::ServerCapabilities {
23839                completion_provider: Some(lsp::CompletionOptions {
23840                    resolve_provider: Some(true),
23841                    ..Default::default()
23842                }),
23843                ..Default::default()
23844            },
23845            ..Default::default()
23846        },
23847    );
23848
23849    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23850    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23851
23852    let worktree_id = workspace
23853        .update(cx, |workspace, _window, cx| {
23854            workspace.project().update(cx, |project, cx| {
23855                project.worktrees(cx).next().unwrap().read(cx).id()
23856            })
23857        })
23858        .unwrap();
23859    project
23860        .update(cx, |project, cx| {
23861            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
23862        })
23863        .await
23864        .unwrap();
23865    let editor = workspace
23866        .update(cx, |workspace, window, cx| {
23867            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
23868        })
23869        .unwrap()
23870        .await
23871        .unwrap()
23872        .downcast::<Editor>()
23873        .unwrap();
23874
23875    let fake_server = fake_servers.next().await.unwrap();
23876    editor.update_in(cx, |editor, window, cx| {
23877        editor.set_text("<ad></ad>", window, cx);
23878        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23879            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
23880        });
23881        set_linked_edit_ranges(
23882            (Point::new(0, 1), Point::new(0, 3)),
23883            (Point::new(0, 6), Point::new(0, 8)),
23884            editor,
23885            cx,
23886        );
23887    });
23888    let mut completion_handle =
23889        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
23890            Ok(Some(lsp::CompletionResponse::Array(vec![
23891                lsp::CompletionItem {
23892                    label: "head".to_string(),
23893                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23894                        lsp::InsertReplaceEdit {
23895                            new_text: "head".to_string(),
23896                            insert: lsp::Range::new(
23897                                lsp::Position::new(0, 1),
23898                                lsp::Position::new(0, 3),
23899                            ),
23900                            replace: lsp::Range::new(
23901                                lsp::Position::new(0, 1),
23902                                lsp::Position::new(0, 3),
23903                            ),
23904                        },
23905                    )),
23906                    ..Default::default()
23907                },
23908            ])))
23909        });
23910    editor.update_in(cx, |editor, window, cx| {
23911        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
23912    });
23913    cx.run_until_parked();
23914    completion_handle.next().await.unwrap();
23915    editor.update(cx, |editor, _| {
23916        assert!(
23917            editor.context_menu_visible(),
23918            "Completion menu should be visible"
23919        );
23920    });
23921    editor.update_in(cx, |editor, window, cx| {
23922        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
23923    });
23924    cx.executor().run_until_parked();
23925    editor.update(cx, |editor, cx| {
23926        assert_eq!(editor.text(cx), "<head></head>");
23927    });
23928}
23929
23930#[gpui::test]
23931async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
23932    init_test(cx, |_| {});
23933
23934    let mut cx = EditorTestContext::new(cx).await;
23935    let language = Arc::new(Language::new(
23936        LanguageConfig {
23937            name: "TSX".into(),
23938            matcher: LanguageMatcher {
23939                path_suffixes: vec!["tsx".to_string()],
23940                ..LanguageMatcher::default()
23941            },
23942            brackets: BracketPairConfig {
23943                pairs: vec![BracketPair {
23944                    start: "<".into(),
23945                    end: ">".into(),
23946                    close: true,
23947                    ..Default::default()
23948                }],
23949                ..Default::default()
23950            },
23951            linked_edit_characters: HashSet::from_iter(['.']),
23952            ..Default::default()
23953        },
23954        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
23955    ));
23956    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23957
23958    // Test typing > does not extend linked pair
23959    cx.set_state("<divˇ<div></div>");
23960    cx.update_editor(|editor, _, cx| {
23961        set_linked_edit_ranges(
23962            (Point::new(0, 1), Point::new(0, 4)),
23963            (Point::new(0, 11), Point::new(0, 14)),
23964            editor,
23965            cx,
23966        );
23967    });
23968    cx.update_editor(|editor, window, cx| {
23969        editor.handle_input(">", window, cx);
23970    });
23971    cx.assert_editor_state("<div>ˇ<div></div>");
23972
23973    // Test typing . do extend linked pair
23974    cx.set_state("<Animatedˇ></Animated>");
23975    cx.update_editor(|editor, _, cx| {
23976        set_linked_edit_ranges(
23977            (Point::new(0, 1), Point::new(0, 9)),
23978            (Point::new(0, 12), Point::new(0, 20)),
23979            editor,
23980            cx,
23981        );
23982    });
23983    cx.update_editor(|editor, window, cx| {
23984        editor.handle_input(".", window, cx);
23985    });
23986    cx.assert_editor_state("<Animated.ˇ></Animated.>");
23987    cx.update_editor(|editor, _, cx| {
23988        set_linked_edit_ranges(
23989            (Point::new(0, 1), Point::new(0, 10)),
23990            (Point::new(0, 13), Point::new(0, 21)),
23991            editor,
23992            cx,
23993        );
23994    });
23995    cx.update_editor(|editor, window, cx| {
23996        editor.handle_input("V", window, cx);
23997    });
23998    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
23999}
24000
24001#[gpui::test]
24002async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
24003    init_test(cx, |_| {});
24004
24005    let fs = FakeFs::new(cx.executor());
24006    fs.insert_tree(
24007        path!("/root"),
24008        json!({
24009            "a": {
24010                "main.rs": "fn main() {}",
24011            },
24012            "foo": {
24013                "bar": {
24014                    "external_file.rs": "pub mod external {}",
24015                }
24016            }
24017        }),
24018    )
24019    .await;
24020
24021    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
24022    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24023    language_registry.add(rust_lang());
24024    let _fake_servers = language_registry.register_fake_lsp(
24025        "Rust",
24026        FakeLspAdapter {
24027            ..FakeLspAdapter::default()
24028        },
24029    );
24030    let (workspace, cx) =
24031        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24032    let worktree_id = workspace.update(cx, |workspace, cx| {
24033        workspace.project().update(cx, |project, cx| {
24034            project.worktrees(cx).next().unwrap().read(cx).id()
24035        })
24036    });
24037
24038    let assert_language_servers_count =
24039        |expected: usize, context: &str, cx: &mut VisualTestContext| {
24040            project.update(cx, |project, cx| {
24041                let current = project
24042                    .lsp_store()
24043                    .read(cx)
24044                    .as_local()
24045                    .unwrap()
24046                    .language_servers
24047                    .len();
24048                assert_eq!(expected, current, "{context}");
24049            });
24050        };
24051
24052    assert_language_servers_count(
24053        0,
24054        "No servers should be running before any file is open",
24055        cx,
24056    );
24057    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24058    let main_editor = workspace
24059        .update_in(cx, |workspace, window, cx| {
24060            workspace.open_path(
24061                (worktree_id, rel_path("main.rs")),
24062                Some(pane.downgrade()),
24063                true,
24064                window,
24065                cx,
24066            )
24067        })
24068        .unwrap()
24069        .await
24070        .downcast::<Editor>()
24071        .unwrap();
24072    pane.update(cx, |pane, cx| {
24073        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24074        open_editor.update(cx, |editor, cx| {
24075            assert_eq!(
24076                editor.display_text(cx),
24077                "fn main() {}",
24078                "Original main.rs text on initial open",
24079            );
24080        });
24081        assert_eq!(open_editor, main_editor);
24082    });
24083    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24084
24085    let external_editor = workspace
24086        .update_in(cx, |workspace, window, cx| {
24087            workspace.open_abs_path(
24088                PathBuf::from("/root/foo/bar/external_file.rs"),
24089                OpenOptions::default(),
24090                window,
24091                cx,
24092            )
24093        })
24094        .await
24095        .expect("opening external file")
24096        .downcast::<Editor>()
24097        .expect("downcasted external file's open element to editor");
24098    pane.update(cx, |pane, cx| {
24099        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24100        open_editor.update(cx, |editor, cx| {
24101            assert_eq!(
24102                editor.display_text(cx),
24103                "pub mod external {}",
24104                "External file is open now",
24105            );
24106        });
24107        assert_eq!(open_editor, external_editor);
24108    });
24109    assert_language_servers_count(
24110        1,
24111        "Second, external, *.rs file should join the existing server",
24112        cx,
24113    );
24114
24115    pane.update_in(cx, |pane, window, cx| {
24116        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24117    })
24118    .await
24119    .unwrap();
24120    pane.update_in(cx, |pane, window, cx| {
24121        pane.navigate_backward(&Default::default(), window, cx);
24122    });
24123    cx.run_until_parked();
24124    pane.update(cx, |pane, cx| {
24125        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24126        open_editor.update(cx, |editor, cx| {
24127            assert_eq!(
24128                editor.display_text(cx),
24129                "pub mod external {}",
24130                "External file is open now",
24131            );
24132        });
24133    });
24134    assert_language_servers_count(
24135        1,
24136        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
24137        cx,
24138    );
24139
24140    cx.update(|_, cx| {
24141        workspace::reload(cx);
24142    });
24143    assert_language_servers_count(
24144        1,
24145        "After reloading the worktree with local and external files opened, only one project should be started",
24146        cx,
24147    );
24148}
24149
24150#[gpui::test]
24151async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
24152    init_test(cx, |_| {});
24153
24154    let mut cx = EditorTestContext::new(cx).await;
24155    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24156    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24157
24158    // test cursor move to start of each line on tab
24159    // for `if`, `elif`, `else`, `while`, `with` and `for`
24160    cx.set_state(indoc! {"
24161        def main():
24162        ˇ    for item in items:
24163        ˇ        while item.active:
24164        ˇ            if item.value > 10:
24165        ˇ                continue
24166        ˇ            elif item.value < 0:
24167        ˇ                break
24168        ˇ            else:
24169        ˇ                with item.context() as ctx:
24170        ˇ                    yield count
24171        ˇ        else:
24172        ˇ            log('while else')
24173        ˇ    else:
24174        ˇ        log('for else')
24175    "});
24176    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24177    cx.assert_editor_state(indoc! {"
24178        def main():
24179            ˇfor item in items:
24180                ˇwhile item.active:
24181                    ˇif item.value > 10:
24182                        ˇcontinue
24183                    ˇelif item.value < 0:
24184                        ˇbreak
24185                    ˇelse:
24186                        ˇwith item.context() as ctx:
24187                            ˇyield count
24188                ˇelse:
24189                    ˇlog('while else')
24190            ˇelse:
24191                ˇlog('for else')
24192    "});
24193    // test relative indent is preserved when tab
24194    // for `if`, `elif`, `else`, `while`, `with` and `for`
24195    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24196    cx.assert_editor_state(indoc! {"
24197        def main():
24198                ˇfor item in items:
24199                    ˇwhile item.active:
24200                        ˇif item.value > 10:
24201                            ˇcontinue
24202                        ˇelif item.value < 0:
24203                            ˇbreak
24204                        ˇelse:
24205                            ˇwith item.context() as ctx:
24206                                ˇyield count
24207                    ˇelse:
24208                        ˇlog('while else')
24209                ˇelse:
24210                    ˇlog('for else')
24211    "});
24212
24213    // test cursor move to start of each line on tab
24214    // for `try`, `except`, `else`, `finally`, `match` and `def`
24215    cx.set_state(indoc! {"
24216        def main():
24217        ˇ    try:
24218        ˇ        fetch()
24219        ˇ    except ValueError:
24220        ˇ        handle_error()
24221        ˇ    else:
24222        ˇ        match value:
24223        ˇ            case _:
24224        ˇ    finally:
24225        ˇ        def status():
24226        ˇ            return 0
24227    "});
24228    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24229    cx.assert_editor_state(indoc! {"
24230        def main():
24231            ˇtry:
24232                ˇfetch()
24233            ˇexcept ValueError:
24234                ˇhandle_error()
24235            ˇelse:
24236                ˇmatch value:
24237                    ˇcase _:
24238            ˇfinally:
24239                ˇdef status():
24240                    ˇreturn 0
24241    "});
24242    // test relative indent is preserved when tab
24243    // for `try`, `except`, `else`, `finally`, `match` and `def`
24244    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24245    cx.assert_editor_state(indoc! {"
24246        def main():
24247                ˇtry:
24248                    ˇfetch()
24249                ˇexcept ValueError:
24250                    ˇhandle_error()
24251                ˇelse:
24252                    ˇmatch value:
24253                        ˇcase _:
24254                ˇfinally:
24255                    ˇdef status():
24256                        ˇreturn 0
24257    "});
24258}
24259
24260#[gpui::test]
24261async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
24262    init_test(cx, |_| {});
24263
24264    let mut cx = EditorTestContext::new(cx).await;
24265    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24266    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24267
24268    // test `else` auto outdents when typed inside `if` block
24269    cx.set_state(indoc! {"
24270        def main():
24271            if i == 2:
24272                return
24273                ˇ
24274    "});
24275    cx.update_editor(|editor, window, cx| {
24276        editor.handle_input("else:", window, cx);
24277    });
24278    cx.assert_editor_state(indoc! {"
24279        def main():
24280            if i == 2:
24281                return
24282            else:ˇ
24283    "});
24284
24285    // test `except` auto outdents when typed inside `try` block
24286    cx.set_state(indoc! {"
24287        def main():
24288            try:
24289                i = 2
24290                ˇ
24291    "});
24292    cx.update_editor(|editor, window, cx| {
24293        editor.handle_input("except:", window, cx);
24294    });
24295    cx.assert_editor_state(indoc! {"
24296        def main():
24297            try:
24298                i = 2
24299            except:ˇ
24300    "});
24301
24302    // test `else` auto outdents when typed inside `except` block
24303    cx.set_state(indoc! {"
24304        def main():
24305            try:
24306                i = 2
24307            except:
24308                j = 2
24309                ˇ
24310    "});
24311    cx.update_editor(|editor, window, cx| {
24312        editor.handle_input("else:", window, cx);
24313    });
24314    cx.assert_editor_state(indoc! {"
24315        def main():
24316            try:
24317                i = 2
24318            except:
24319                j = 2
24320            else:ˇ
24321    "});
24322
24323    // test `finally` auto outdents when typed inside `else` block
24324    cx.set_state(indoc! {"
24325        def main():
24326            try:
24327                i = 2
24328            except:
24329                j = 2
24330            else:
24331                k = 2
24332                ˇ
24333    "});
24334    cx.update_editor(|editor, window, cx| {
24335        editor.handle_input("finally:", window, cx);
24336    });
24337    cx.assert_editor_state(indoc! {"
24338        def main():
24339            try:
24340                i = 2
24341            except:
24342                j = 2
24343            else:
24344                k = 2
24345            finally:ˇ
24346    "});
24347
24348    // test `else` does not outdents when typed inside `except` block right after for block
24349    cx.set_state(indoc! {"
24350        def main():
24351            try:
24352                i = 2
24353            except:
24354                for i in range(n):
24355                    pass
24356                ˇ
24357    "});
24358    cx.update_editor(|editor, window, cx| {
24359        editor.handle_input("else:", window, cx);
24360    });
24361    cx.assert_editor_state(indoc! {"
24362        def main():
24363            try:
24364                i = 2
24365            except:
24366                for i in range(n):
24367                    pass
24368                else:ˇ
24369    "});
24370
24371    // test `finally` auto outdents when typed inside `else` block right after for block
24372    cx.set_state(indoc! {"
24373        def main():
24374            try:
24375                i = 2
24376            except:
24377                j = 2
24378            else:
24379                for i in range(n):
24380                    pass
24381                ˇ
24382    "});
24383    cx.update_editor(|editor, window, cx| {
24384        editor.handle_input("finally:", window, cx);
24385    });
24386    cx.assert_editor_state(indoc! {"
24387        def main():
24388            try:
24389                i = 2
24390            except:
24391                j = 2
24392            else:
24393                for i in range(n):
24394                    pass
24395            finally:ˇ
24396    "});
24397
24398    // test `except` outdents to inner "try" block
24399    cx.set_state(indoc! {"
24400        def main():
24401            try:
24402                i = 2
24403                if i == 2:
24404                    try:
24405                        i = 3
24406                        ˇ
24407    "});
24408    cx.update_editor(|editor, window, cx| {
24409        editor.handle_input("except:", window, cx);
24410    });
24411    cx.assert_editor_state(indoc! {"
24412        def main():
24413            try:
24414                i = 2
24415                if i == 2:
24416                    try:
24417                        i = 3
24418                    except:ˇ
24419    "});
24420
24421    // test `except` outdents to outer "try" block
24422    cx.set_state(indoc! {"
24423        def main():
24424            try:
24425                i = 2
24426                if i == 2:
24427                    try:
24428                        i = 3
24429                ˇ
24430    "});
24431    cx.update_editor(|editor, window, cx| {
24432        editor.handle_input("except:", window, cx);
24433    });
24434    cx.assert_editor_state(indoc! {"
24435        def main():
24436            try:
24437                i = 2
24438                if i == 2:
24439                    try:
24440                        i = 3
24441            except:ˇ
24442    "});
24443
24444    // test `else` stays at correct indent when typed after `for` block
24445    cx.set_state(indoc! {"
24446        def main():
24447            for i in range(10):
24448                if i == 3:
24449                    break
24450            ˇ
24451    "});
24452    cx.update_editor(|editor, window, cx| {
24453        editor.handle_input("else:", window, cx);
24454    });
24455    cx.assert_editor_state(indoc! {"
24456        def main():
24457            for i in range(10):
24458                if i == 3:
24459                    break
24460            else:ˇ
24461    "});
24462
24463    // test does not outdent on typing after line with square brackets
24464    cx.set_state(indoc! {"
24465        def f() -> list[str]:
24466            ˇ
24467    "});
24468    cx.update_editor(|editor, window, cx| {
24469        editor.handle_input("a", window, cx);
24470    });
24471    cx.assert_editor_state(indoc! {"
24472        def f() -> list[str]:
2447324474    "});
24475
24476    // test does not outdent on typing : after case keyword
24477    cx.set_state(indoc! {"
24478        match 1:
24479            caseˇ
24480    "});
24481    cx.update_editor(|editor, window, cx| {
24482        editor.handle_input(":", window, cx);
24483    });
24484    cx.assert_editor_state(indoc! {"
24485        match 1:
24486            case:ˇ
24487    "});
24488}
24489
24490#[gpui::test]
24491async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
24492    init_test(cx, |_| {});
24493    update_test_language_settings(cx, |settings| {
24494        settings.defaults.extend_comment_on_newline = Some(false);
24495    });
24496    let mut cx = EditorTestContext::new(cx).await;
24497    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24498    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24499
24500    // test correct indent after newline on comment
24501    cx.set_state(indoc! {"
24502        # COMMENT:ˇ
24503    "});
24504    cx.update_editor(|editor, window, cx| {
24505        editor.newline(&Newline, window, cx);
24506    });
24507    cx.assert_editor_state(indoc! {"
24508        # COMMENT:
24509        ˇ
24510    "});
24511
24512    // test correct indent after newline in brackets
24513    cx.set_state(indoc! {"
24514        {ˇ}
24515    "});
24516    cx.update_editor(|editor, window, cx| {
24517        editor.newline(&Newline, window, cx);
24518    });
24519    cx.run_until_parked();
24520    cx.assert_editor_state(indoc! {"
24521        {
24522            ˇ
24523        }
24524    "});
24525
24526    cx.set_state(indoc! {"
24527        (ˇ)
24528    "});
24529    cx.update_editor(|editor, window, cx| {
24530        editor.newline(&Newline, window, cx);
24531    });
24532    cx.run_until_parked();
24533    cx.assert_editor_state(indoc! {"
24534        (
24535            ˇ
24536        )
24537    "});
24538
24539    // do not indent after empty lists or dictionaries
24540    cx.set_state(indoc! {"
24541        a = []ˇ
24542    "});
24543    cx.update_editor(|editor, window, cx| {
24544        editor.newline(&Newline, window, cx);
24545    });
24546    cx.run_until_parked();
24547    cx.assert_editor_state(indoc! {"
24548        a = []
24549        ˇ
24550    "});
24551}
24552
24553#[gpui::test]
24554async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
24555    init_test(cx, |_| {});
24556
24557    let mut cx = EditorTestContext::new(cx).await;
24558    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24559    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24560
24561    // test cursor move to start of each line on tab
24562    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
24563    cx.set_state(indoc! {"
24564        function main() {
24565        ˇ    for item in $items; do
24566        ˇ        while [ -n \"$item\" ]; do
24567        ˇ            if [ \"$value\" -gt 10 ]; then
24568        ˇ                continue
24569        ˇ            elif [ \"$value\" -lt 0 ]; then
24570        ˇ                break
24571        ˇ            else
24572        ˇ                echo \"$item\"
24573        ˇ            fi
24574        ˇ        done
24575        ˇ    done
24576        ˇ}
24577    "});
24578    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24579    cx.assert_editor_state(indoc! {"
24580        function main() {
24581            ˇfor item in $items; do
24582                ˇwhile [ -n \"$item\" ]; do
24583                    ˇif [ \"$value\" -gt 10 ]; then
24584                        ˇcontinue
24585                    ˇelif [ \"$value\" -lt 0 ]; then
24586                        ˇbreak
24587                    ˇelse
24588                        ˇecho \"$item\"
24589                    ˇfi
24590                ˇdone
24591            ˇdone
24592        ˇ}
24593    "});
24594    // test relative indent is preserved when tab
24595    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24596    cx.assert_editor_state(indoc! {"
24597        function main() {
24598                ˇfor item in $items; do
24599                    ˇwhile [ -n \"$item\" ]; do
24600                        ˇif [ \"$value\" -gt 10 ]; then
24601                            ˇcontinue
24602                        ˇelif [ \"$value\" -lt 0 ]; then
24603                            ˇbreak
24604                        ˇelse
24605                            ˇecho \"$item\"
24606                        ˇfi
24607                    ˇdone
24608                ˇdone
24609            ˇ}
24610    "});
24611
24612    // test cursor move to start of each line on tab
24613    // for `case` statement with patterns
24614    cx.set_state(indoc! {"
24615        function handle() {
24616        ˇ    case \"$1\" in
24617        ˇ        start)
24618        ˇ            echo \"a\"
24619        ˇ            ;;
24620        ˇ        stop)
24621        ˇ            echo \"b\"
24622        ˇ            ;;
24623        ˇ        *)
24624        ˇ            echo \"c\"
24625        ˇ            ;;
24626        ˇ    esac
24627        ˇ}
24628    "});
24629    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24630    cx.assert_editor_state(indoc! {"
24631        function handle() {
24632            ˇcase \"$1\" in
24633                ˇstart)
24634                    ˇecho \"a\"
24635                    ˇ;;
24636                ˇstop)
24637                    ˇecho \"b\"
24638                    ˇ;;
24639                ˇ*)
24640                    ˇecho \"c\"
24641                    ˇ;;
24642            ˇesac
24643        ˇ}
24644    "});
24645}
24646
24647#[gpui::test]
24648async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
24649    init_test(cx, |_| {});
24650
24651    let mut cx = EditorTestContext::new(cx).await;
24652    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24653    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24654
24655    // test indents on comment insert
24656    cx.set_state(indoc! {"
24657        function main() {
24658        ˇ    for item in $items; do
24659        ˇ        while [ -n \"$item\" ]; do
24660        ˇ            if [ \"$value\" -gt 10 ]; then
24661        ˇ                continue
24662        ˇ            elif [ \"$value\" -lt 0 ]; then
24663        ˇ                break
24664        ˇ            else
24665        ˇ                echo \"$item\"
24666        ˇ            fi
24667        ˇ        done
24668        ˇ    done
24669        ˇ}
24670    "});
24671    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
24672    cx.assert_editor_state(indoc! {"
24673        function main() {
24674        #ˇ    for item in $items; do
24675        #ˇ        while [ -n \"$item\" ]; do
24676        #ˇ            if [ \"$value\" -gt 10 ]; then
24677        #ˇ                continue
24678        #ˇ            elif [ \"$value\" -lt 0 ]; then
24679        #ˇ                break
24680        #ˇ            else
24681        #ˇ                echo \"$item\"
24682        #ˇ            fi
24683        #ˇ        done
24684        #ˇ    done
24685        #ˇ}
24686    "});
24687}
24688
24689#[gpui::test]
24690async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
24691    init_test(cx, |_| {});
24692
24693    let mut cx = EditorTestContext::new(cx).await;
24694    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24695    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24696
24697    // test `else` auto outdents when typed inside `if` block
24698    cx.set_state(indoc! {"
24699        if [ \"$1\" = \"test\" ]; then
24700            echo \"foo bar\"
24701            ˇ
24702    "});
24703    cx.update_editor(|editor, window, cx| {
24704        editor.handle_input("else", window, cx);
24705    });
24706    cx.assert_editor_state(indoc! {"
24707        if [ \"$1\" = \"test\" ]; then
24708            echo \"foo bar\"
24709        elseˇ
24710    "});
24711
24712    // test `elif` auto outdents when typed inside `if` block
24713    cx.set_state(indoc! {"
24714        if [ \"$1\" = \"test\" ]; then
24715            echo \"foo bar\"
24716            ˇ
24717    "});
24718    cx.update_editor(|editor, window, cx| {
24719        editor.handle_input("elif", window, cx);
24720    });
24721    cx.assert_editor_state(indoc! {"
24722        if [ \"$1\" = \"test\" ]; then
24723            echo \"foo bar\"
24724        elifˇ
24725    "});
24726
24727    // test `fi` auto outdents when typed inside `else` block
24728    cx.set_state(indoc! {"
24729        if [ \"$1\" = \"test\" ]; then
24730            echo \"foo bar\"
24731        else
24732            echo \"bar baz\"
24733            ˇ
24734    "});
24735    cx.update_editor(|editor, window, cx| {
24736        editor.handle_input("fi", window, cx);
24737    });
24738    cx.assert_editor_state(indoc! {"
24739        if [ \"$1\" = \"test\" ]; then
24740            echo \"foo bar\"
24741        else
24742            echo \"bar baz\"
24743        fiˇ
24744    "});
24745
24746    // test `done` auto outdents when typed inside `while` block
24747    cx.set_state(indoc! {"
24748        while read line; do
24749            echo \"$line\"
24750            ˇ
24751    "});
24752    cx.update_editor(|editor, window, cx| {
24753        editor.handle_input("done", window, cx);
24754    });
24755    cx.assert_editor_state(indoc! {"
24756        while read line; do
24757            echo \"$line\"
24758        doneˇ
24759    "});
24760
24761    // test `done` auto outdents when typed inside `for` block
24762    cx.set_state(indoc! {"
24763        for file in *.txt; do
24764            cat \"$file\"
24765            ˇ
24766    "});
24767    cx.update_editor(|editor, window, cx| {
24768        editor.handle_input("done", window, cx);
24769    });
24770    cx.assert_editor_state(indoc! {"
24771        for file in *.txt; do
24772            cat \"$file\"
24773        doneˇ
24774    "});
24775
24776    // test `esac` auto outdents when typed inside `case` block
24777    cx.set_state(indoc! {"
24778        case \"$1\" in
24779            start)
24780                echo \"foo bar\"
24781                ;;
24782            stop)
24783                echo \"bar baz\"
24784                ;;
24785            ˇ
24786    "});
24787    cx.update_editor(|editor, window, cx| {
24788        editor.handle_input("esac", window, cx);
24789    });
24790    cx.assert_editor_state(indoc! {"
24791        case \"$1\" in
24792            start)
24793                echo \"foo bar\"
24794                ;;
24795            stop)
24796                echo \"bar baz\"
24797                ;;
24798        esacˇ
24799    "});
24800
24801    // test `*)` auto outdents when typed inside `case` block
24802    cx.set_state(indoc! {"
24803        case \"$1\" in
24804            start)
24805                echo \"foo bar\"
24806                ;;
24807                ˇ
24808    "});
24809    cx.update_editor(|editor, window, cx| {
24810        editor.handle_input("*)", window, cx);
24811    });
24812    cx.assert_editor_state(indoc! {"
24813        case \"$1\" in
24814            start)
24815                echo \"foo bar\"
24816                ;;
24817            *)ˇ
24818    "});
24819
24820    // test `fi` outdents to correct level with nested if blocks
24821    cx.set_state(indoc! {"
24822        if [ \"$1\" = \"test\" ]; then
24823            echo \"outer if\"
24824            if [ \"$2\" = \"debug\" ]; then
24825                echo \"inner if\"
24826                ˇ
24827    "});
24828    cx.update_editor(|editor, window, cx| {
24829        editor.handle_input("fi", window, cx);
24830    });
24831    cx.assert_editor_state(indoc! {"
24832        if [ \"$1\" = \"test\" ]; then
24833            echo \"outer if\"
24834            if [ \"$2\" = \"debug\" ]; then
24835                echo \"inner if\"
24836            fiˇ
24837    "});
24838}
24839
24840#[gpui::test]
24841async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
24842    init_test(cx, |_| {});
24843    update_test_language_settings(cx, |settings| {
24844        settings.defaults.extend_comment_on_newline = Some(false);
24845    });
24846    let mut cx = EditorTestContext::new(cx).await;
24847    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24848    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24849
24850    // test correct indent after newline on comment
24851    cx.set_state(indoc! {"
24852        # COMMENT:ˇ
24853    "});
24854    cx.update_editor(|editor, window, cx| {
24855        editor.newline(&Newline, window, cx);
24856    });
24857    cx.assert_editor_state(indoc! {"
24858        # COMMENT:
24859        ˇ
24860    "});
24861
24862    // test correct indent after newline after `then`
24863    cx.set_state(indoc! {"
24864
24865        if [ \"$1\" = \"test\" ]; thenˇ
24866    "});
24867    cx.update_editor(|editor, window, cx| {
24868        editor.newline(&Newline, window, cx);
24869    });
24870    cx.run_until_parked();
24871    cx.assert_editor_state(indoc! {"
24872
24873        if [ \"$1\" = \"test\" ]; then
24874            ˇ
24875    "});
24876
24877    // test correct indent after newline after `else`
24878    cx.set_state(indoc! {"
24879        if [ \"$1\" = \"test\" ]; then
24880        elseˇ
24881    "});
24882    cx.update_editor(|editor, window, cx| {
24883        editor.newline(&Newline, window, cx);
24884    });
24885    cx.run_until_parked();
24886    cx.assert_editor_state(indoc! {"
24887        if [ \"$1\" = \"test\" ]; then
24888        else
24889            ˇ
24890    "});
24891
24892    // test correct indent after newline after `elif`
24893    cx.set_state(indoc! {"
24894        if [ \"$1\" = \"test\" ]; then
24895        elifˇ
24896    "});
24897    cx.update_editor(|editor, window, cx| {
24898        editor.newline(&Newline, window, cx);
24899    });
24900    cx.run_until_parked();
24901    cx.assert_editor_state(indoc! {"
24902        if [ \"$1\" = \"test\" ]; then
24903        elif
24904            ˇ
24905    "});
24906
24907    // test correct indent after newline after `do`
24908    cx.set_state(indoc! {"
24909        for file in *.txt; doˇ
24910    "});
24911    cx.update_editor(|editor, window, cx| {
24912        editor.newline(&Newline, window, cx);
24913    });
24914    cx.run_until_parked();
24915    cx.assert_editor_state(indoc! {"
24916        for file in *.txt; do
24917            ˇ
24918    "});
24919
24920    // test correct indent after newline after case pattern
24921    cx.set_state(indoc! {"
24922        case \"$1\" in
24923            start)ˇ
24924    "});
24925    cx.update_editor(|editor, window, cx| {
24926        editor.newline(&Newline, window, cx);
24927    });
24928    cx.run_until_parked();
24929    cx.assert_editor_state(indoc! {"
24930        case \"$1\" in
24931            start)
24932                ˇ
24933    "});
24934
24935    // test correct indent after newline after case pattern
24936    cx.set_state(indoc! {"
24937        case \"$1\" in
24938            start)
24939                ;;
24940            *)ˇ
24941    "});
24942    cx.update_editor(|editor, window, cx| {
24943        editor.newline(&Newline, window, cx);
24944    });
24945    cx.run_until_parked();
24946    cx.assert_editor_state(indoc! {"
24947        case \"$1\" in
24948            start)
24949                ;;
24950            *)
24951                ˇ
24952    "});
24953
24954    // test correct indent after newline after function opening brace
24955    cx.set_state(indoc! {"
24956        function test() {ˇ}
24957    "});
24958    cx.update_editor(|editor, window, cx| {
24959        editor.newline(&Newline, window, cx);
24960    });
24961    cx.run_until_parked();
24962    cx.assert_editor_state(indoc! {"
24963        function test() {
24964            ˇ
24965        }
24966    "});
24967
24968    // test no extra indent after semicolon on same line
24969    cx.set_state(indoc! {"
24970        echo \"test\"24971    "});
24972    cx.update_editor(|editor, window, cx| {
24973        editor.newline(&Newline, window, cx);
24974    });
24975    cx.run_until_parked();
24976    cx.assert_editor_state(indoc! {"
24977        echo \"test\";
24978        ˇ
24979    "});
24980}
24981
24982fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
24983    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
24984    point..point
24985}
24986
24987#[track_caller]
24988fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
24989    let (text, ranges) = marked_text_ranges(marked_text, true);
24990    assert_eq!(editor.text(cx), text);
24991    assert_eq!(
24992        editor.selections.ranges(cx),
24993        ranges,
24994        "Assert selections are {}",
24995        marked_text
24996    );
24997}
24998
24999pub fn handle_signature_help_request(
25000    cx: &mut EditorLspTestContext,
25001    mocked_response: lsp::SignatureHelp,
25002) -> impl Future<Output = ()> + use<> {
25003    let mut request =
25004        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
25005            let mocked_response = mocked_response.clone();
25006            async move { Ok(Some(mocked_response)) }
25007        });
25008
25009    async move {
25010        request.next().await;
25011    }
25012}
25013
25014#[track_caller]
25015pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
25016    cx.update_editor(|editor, _, _| {
25017        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
25018            let entries = menu.entries.borrow();
25019            let entries = entries
25020                .iter()
25021                .map(|entry| entry.string.as_str())
25022                .collect::<Vec<_>>();
25023            assert_eq!(entries, expected);
25024        } else {
25025            panic!("Expected completions menu");
25026        }
25027    });
25028}
25029
25030/// Handle completion request passing a marked string specifying where the completion
25031/// should be triggered from using '|' character, what range should be replaced, and what completions
25032/// should be returned using '<' and '>' to delimit the range.
25033///
25034/// Also see `handle_completion_request_with_insert_and_replace`.
25035#[track_caller]
25036pub fn handle_completion_request(
25037    marked_string: &str,
25038    completions: Vec<&'static str>,
25039    is_incomplete: bool,
25040    counter: Arc<AtomicUsize>,
25041    cx: &mut EditorLspTestContext,
25042) -> impl Future<Output = ()> {
25043    let complete_from_marker: TextRangeMarker = '|'.into();
25044    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25045    let (_, mut marked_ranges) = marked_text_ranges_by(
25046        marked_string,
25047        vec![complete_from_marker.clone(), replace_range_marker.clone()],
25048    );
25049
25050    let complete_from_position =
25051        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25052    let replace_range =
25053        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25054
25055    let mut request =
25056        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25057            let completions = completions.clone();
25058            counter.fetch_add(1, atomic::Ordering::Release);
25059            async move {
25060                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25061                assert_eq!(
25062                    params.text_document_position.position,
25063                    complete_from_position
25064                );
25065                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
25066                    is_incomplete,
25067                    item_defaults: None,
25068                    items: completions
25069                        .iter()
25070                        .map(|completion_text| lsp::CompletionItem {
25071                            label: completion_text.to_string(),
25072                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
25073                                range: replace_range,
25074                                new_text: completion_text.to_string(),
25075                            })),
25076                            ..Default::default()
25077                        })
25078                        .collect(),
25079                })))
25080            }
25081        });
25082
25083    async move {
25084        request.next().await;
25085    }
25086}
25087
25088/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
25089/// given instead, which also contains an `insert` range.
25090///
25091/// This function uses markers to define ranges:
25092/// - `|` marks the cursor position
25093/// - `<>` marks the replace range
25094/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
25095pub fn handle_completion_request_with_insert_and_replace(
25096    cx: &mut EditorLspTestContext,
25097    marked_string: &str,
25098    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
25099    counter: Arc<AtomicUsize>,
25100) -> impl Future<Output = ()> {
25101    let complete_from_marker: TextRangeMarker = '|'.into();
25102    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25103    let insert_range_marker: TextRangeMarker = ('{', '}').into();
25104
25105    let (_, mut marked_ranges) = marked_text_ranges_by(
25106        marked_string,
25107        vec![
25108            complete_from_marker.clone(),
25109            replace_range_marker.clone(),
25110            insert_range_marker.clone(),
25111        ],
25112    );
25113
25114    let complete_from_position =
25115        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25116    let replace_range =
25117        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25118
25119    let insert_range = match marked_ranges.remove(&insert_range_marker) {
25120        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
25121        _ => lsp::Range {
25122            start: replace_range.start,
25123            end: complete_from_position,
25124        },
25125    };
25126
25127    let mut request =
25128        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25129            let completions = completions.clone();
25130            counter.fetch_add(1, atomic::Ordering::Release);
25131            async move {
25132                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25133                assert_eq!(
25134                    params.text_document_position.position, complete_from_position,
25135                    "marker `|` position doesn't match",
25136                );
25137                Ok(Some(lsp::CompletionResponse::Array(
25138                    completions
25139                        .iter()
25140                        .map(|(label, new_text)| lsp::CompletionItem {
25141                            label: label.to_string(),
25142                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25143                                lsp::InsertReplaceEdit {
25144                                    insert: insert_range,
25145                                    replace: replace_range,
25146                                    new_text: new_text.to_string(),
25147                                },
25148                            )),
25149                            ..Default::default()
25150                        })
25151                        .collect(),
25152                )))
25153            }
25154        });
25155
25156    async move {
25157        request.next().await;
25158    }
25159}
25160
25161fn handle_resolve_completion_request(
25162    cx: &mut EditorLspTestContext,
25163    edits: Option<Vec<(&'static str, &'static str)>>,
25164) -> impl Future<Output = ()> {
25165    let edits = edits.map(|edits| {
25166        edits
25167            .iter()
25168            .map(|(marked_string, new_text)| {
25169                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
25170                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
25171                lsp::TextEdit::new(replace_range, new_text.to_string())
25172            })
25173            .collect::<Vec<_>>()
25174    });
25175
25176    let mut request =
25177        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
25178            let edits = edits.clone();
25179            async move {
25180                Ok(lsp::CompletionItem {
25181                    additional_text_edits: edits,
25182                    ..Default::default()
25183                })
25184            }
25185        });
25186
25187    async move {
25188        request.next().await;
25189    }
25190}
25191
25192pub(crate) fn update_test_language_settings(
25193    cx: &mut TestAppContext,
25194    f: impl Fn(&mut AllLanguageSettingsContent),
25195) {
25196    cx.update(|cx| {
25197        SettingsStore::update_global(cx, |store, cx| {
25198            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
25199        });
25200    });
25201}
25202
25203pub(crate) fn update_test_project_settings(
25204    cx: &mut TestAppContext,
25205    f: impl Fn(&mut ProjectSettingsContent),
25206) {
25207    cx.update(|cx| {
25208        SettingsStore::update_global(cx, |store, cx| {
25209            store.update_user_settings(cx, |settings| f(&mut settings.project));
25210        });
25211    });
25212}
25213
25214pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
25215    cx.update(|cx| {
25216        assets::Assets.load_test_fonts(cx);
25217        let store = SettingsStore::test(cx);
25218        cx.set_global(store);
25219        theme::init(theme::LoadThemes::JustBase, cx);
25220        release_channel::init(SemanticVersion::default(), cx);
25221        client::init_settings(cx);
25222        language::init(cx);
25223        Project::init_settings(cx);
25224        workspace::init_settings(cx);
25225        crate::init(cx);
25226    });
25227    zlog::init_test();
25228    update_test_language_settings(cx, f);
25229}
25230
25231#[track_caller]
25232fn assert_hunk_revert(
25233    not_reverted_text_with_selections: &str,
25234    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
25235    expected_reverted_text_with_selections: &str,
25236    base_text: &str,
25237    cx: &mut EditorLspTestContext,
25238) {
25239    cx.set_state(not_reverted_text_with_selections);
25240    cx.set_head_text(base_text);
25241    cx.executor().run_until_parked();
25242
25243    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
25244        let snapshot = editor.snapshot(window, cx);
25245        let reverted_hunk_statuses = snapshot
25246            .buffer_snapshot
25247            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
25248            .map(|hunk| hunk.status().kind)
25249            .collect::<Vec<_>>();
25250
25251        editor.git_restore(&Default::default(), window, cx);
25252        reverted_hunk_statuses
25253    });
25254    cx.executor().run_until_parked();
25255    cx.assert_editor_state(expected_reverted_text_with_selections);
25256    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
25257}
25258
25259#[gpui::test(iterations = 10)]
25260async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
25261    init_test(cx, |_| {});
25262
25263    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
25264    let counter = diagnostic_requests.clone();
25265
25266    let fs = FakeFs::new(cx.executor());
25267    fs.insert_tree(
25268        path!("/a"),
25269        json!({
25270            "first.rs": "fn main() { let a = 5; }",
25271            "second.rs": "// Test file",
25272        }),
25273    )
25274    .await;
25275
25276    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25277    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25278    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25279
25280    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25281    language_registry.add(rust_lang());
25282    let mut fake_servers = language_registry.register_fake_lsp(
25283        "Rust",
25284        FakeLspAdapter {
25285            capabilities: lsp::ServerCapabilities {
25286                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
25287                    lsp::DiagnosticOptions {
25288                        identifier: None,
25289                        inter_file_dependencies: true,
25290                        workspace_diagnostics: true,
25291                        work_done_progress_options: Default::default(),
25292                    },
25293                )),
25294                ..Default::default()
25295            },
25296            ..Default::default()
25297        },
25298    );
25299
25300    let editor = workspace
25301        .update(cx, |workspace, window, cx| {
25302            workspace.open_abs_path(
25303                PathBuf::from(path!("/a/first.rs")),
25304                OpenOptions::default(),
25305                window,
25306                cx,
25307            )
25308        })
25309        .unwrap()
25310        .await
25311        .unwrap()
25312        .downcast::<Editor>()
25313        .unwrap();
25314    let fake_server = fake_servers.next().await.unwrap();
25315    let server_id = fake_server.server.server_id();
25316    let mut first_request = fake_server
25317        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
25318            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
25319            let result_id = Some(new_result_id.to_string());
25320            assert_eq!(
25321                params.text_document.uri,
25322                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25323            );
25324            async move {
25325                Ok(lsp::DocumentDiagnosticReportResult::Report(
25326                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
25327                        related_documents: None,
25328                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
25329                            items: Vec::new(),
25330                            result_id,
25331                        },
25332                    }),
25333                ))
25334            }
25335        });
25336
25337    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
25338        project.update(cx, |project, cx| {
25339            let buffer_id = editor
25340                .read(cx)
25341                .buffer()
25342                .read(cx)
25343                .as_singleton()
25344                .expect("created a singleton buffer")
25345                .read(cx)
25346                .remote_id();
25347            let buffer_result_id = project
25348                .lsp_store()
25349                .read(cx)
25350                .result_id(server_id, buffer_id, cx);
25351            assert_eq!(expected, buffer_result_id);
25352        });
25353    };
25354
25355    ensure_result_id(None, cx);
25356    cx.executor().advance_clock(Duration::from_millis(60));
25357    cx.executor().run_until_parked();
25358    assert_eq!(
25359        diagnostic_requests.load(atomic::Ordering::Acquire),
25360        1,
25361        "Opening file should trigger diagnostic request"
25362    );
25363    first_request
25364        .next()
25365        .await
25366        .expect("should have sent the first diagnostics pull request");
25367    ensure_result_id(Some("1".to_string()), cx);
25368
25369    // Editing should trigger diagnostics
25370    editor.update_in(cx, |editor, window, cx| {
25371        editor.handle_input("2", window, cx)
25372    });
25373    cx.executor().advance_clock(Duration::from_millis(60));
25374    cx.executor().run_until_parked();
25375    assert_eq!(
25376        diagnostic_requests.load(atomic::Ordering::Acquire),
25377        2,
25378        "Editing should trigger diagnostic request"
25379    );
25380    ensure_result_id(Some("2".to_string()), cx);
25381
25382    // Moving cursor should not trigger diagnostic request
25383    editor.update_in(cx, |editor, window, cx| {
25384        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25385            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
25386        });
25387    });
25388    cx.executor().advance_clock(Duration::from_millis(60));
25389    cx.executor().run_until_parked();
25390    assert_eq!(
25391        diagnostic_requests.load(atomic::Ordering::Acquire),
25392        2,
25393        "Cursor movement should not trigger diagnostic request"
25394    );
25395    ensure_result_id(Some("2".to_string()), cx);
25396    // Multiple rapid edits should be debounced
25397    for _ in 0..5 {
25398        editor.update_in(cx, |editor, window, cx| {
25399            editor.handle_input("x", window, cx)
25400        });
25401    }
25402    cx.executor().advance_clock(Duration::from_millis(60));
25403    cx.executor().run_until_parked();
25404
25405    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
25406    assert!(
25407        final_requests <= 4,
25408        "Multiple rapid edits should be debounced (got {final_requests} requests)",
25409    );
25410    ensure_result_id(Some(final_requests.to_string()), cx);
25411}
25412
25413#[gpui::test]
25414async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
25415    // Regression test for issue #11671
25416    // Previously, adding a cursor after moving multiple cursors would reset
25417    // the cursor count instead of adding to the existing cursors.
25418    init_test(cx, |_| {});
25419    let mut cx = EditorTestContext::new(cx).await;
25420
25421    // Create a simple buffer with cursor at start
25422    cx.set_state(indoc! {"
25423        ˇaaaa
25424        bbbb
25425        cccc
25426        dddd
25427        eeee
25428        ffff
25429        gggg
25430        hhhh"});
25431
25432    // Add 2 cursors below (so we have 3 total)
25433    cx.update_editor(|editor, window, cx| {
25434        editor.add_selection_below(&Default::default(), window, cx);
25435        editor.add_selection_below(&Default::default(), window, cx);
25436    });
25437
25438    // Verify we have 3 cursors
25439    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
25440    assert_eq!(
25441        initial_count, 3,
25442        "Should have 3 cursors after adding 2 below"
25443    );
25444
25445    // Move down one line
25446    cx.update_editor(|editor, window, cx| {
25447        editor.move_down(&MoveDown, window, cx);
25448    });
25449
25450    // Add another cursor below
25451    cx.update_editor(|editor, window, cx| {
25452        editor.add_selection_below(&Default::default(), window, cx);
25453    });
25454
25455    // Should now have 4 cursors (3 original + 1 new)
25456    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
25457    assert_eq!(
25458        final_count, 4,
25459        "Should have 4 cursors after moving and adding another"
25460    );
25461}
25462
25463#[gpui::test(iterations = 10)]
25464async fn test_document_colors(cx: &mut TestAppContext) {
25465    let expected_color = Rgba {
25466        r: 0.33,
25467        g: 0.33,
25468        b: 0.33,
25469        a: 0.33,
25470    };
25471
25472    init_test(cx, |_| {});
25473
25474    let fs = FakeFs::new(cx.executor());
25475    fs.insert_tree(
25476        path!("/a"),
25477        json!({
25478            "first.rs": "fn main() { let a = 5; }",
25479        }),
25480    )
25481    .await;
25482
25483    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25484    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25485    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25486
25487    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25488    language_registry.add(rust_lang());
25489    let mut fake_servers = language_registry.register_fake_lsp(
25490        "Rust",
25491        FakeLspAdapter {
25492            capabilities: lsp::ServerCapabilities {
25493                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
25494                ..lsp::ServerCapabilities::default()
25495            },
25496            name: "rust-analyzer",
25497            ..FakeLspAdapter::default()
25498        },
25499    );
25500    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
25501        "Rust",
25502        FakeLspAdapter {
25503            capabilities: lsp::ServerCapabilities {
25504                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
25505                ..lsp::ServerCapabilities::default()
25506            },
25507            name: "not-rust-analyzer",
25508            ..FakeLspAdapter::default()
25509        },
25510    );
25511
25512    let editor = workspace
25513        .update(cx, |workspace, window, cx| {
25514            workspace.open_abs_path(
25515                PathBuf::from(path!("/a/first.rs")),
25516                OpenOptions::default(),
25517                window,
25518                cx,
25519            )
25520        })
25521        .unwrap()
25522        .await
25523        .unwrap()
25524        .downcast::<Editor>()
25525        .unwrap();
25526    let fake_language_server = fake_servers.next().await.unwrap();
25527    let fake_language_server_without_capabilities =
25528        fake_servers_without_capabilities.next().await.unwrap();
25529    let requests_made = Arc::new(AtomicUsize::new(0));
25530    let closure_requests_made = Arc::clone(&requests_made);
25531    let mut color_request_handle = fake_language_server
25532        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25533            let requests_made = Arc::clone(&closure_requests_made);
25534            async move {
25535                assert_eq!(
25536                    params.text_document.uri,
25537                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25538                );
25539                requests_made.fetch_add(1, atomic::Ordering::Release);
25540                Ok(vec![
25541                    lsp::ColorInformation {
25542                        range: lsp::Range {
25543                            start: lsp::Position {
25544                                line: 0,
25545                                character: 0,
25546                            },
25547                            end: lsp::Position {
25548                                line: 0,
25549                                character: 1,
25550                            },
25551                        },
25552                        color: lsp::Color {
25553                            red: 0.33,
25554                            green: 0.33,
25555                            blue: 0.33,
25556                            alpha: 0.33,
25557                        },
25558                    },
25559                    lsp::ColorInformation {
25560                        range: lsp::Range {
25561                            start: lsp::Position {
25562                                line: 0,
25563                                character: 0,
25564                            },
25565                            end: lsp::Position {
25566                                line: 0,
25567                                character: 1,
25568                            },
25569                        },
25570                        color: lsp::Color {
25571                            red: 0.33,
25572                            green: 0.33,
25573                            blue: 0.33,
25574                            alpha: 0.33,
25575                        },
25576                    },
25577                ])
25578            }
25579        });
25580
25581    let _handle = fake_language_server_without_capabilities
25582        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
25583            panic!("Should not be called");
25584        });
25585    cx.executor().advance_clock(Duration::from_millis(100));
25586    color_request_handle.next().await.unwrap();
25587    cx.run_until_parked();
25588    assert_eq!(
25589        1,
25590        requests_made.load(atomic::Ordering::Acquire),
25591        "Should query for colors once per editor open"
25592    );
25593    editor.update_in(cx, |editor, _, cx| {
25594        assert_eq!(
25595            vec![expected_color],
25596            extract_color_inlays(editor, cx),
25597            "Should have an initial inlay"
25598        );
25599    });
25600
25601    // opening another file in a split should not influence the LSP query counter
25602    workspace
25603        .update(cx, |workspace, window, cx| {
25604            assert_eq!(
25605                workspace.panes().len(),
25606                1,
25607                "Should have one pane with one editor"
25608            );
25609            workspace.move_item_to_pane_in_direction(
25610                &MoveItemToPaneInDirection {
25611                    direction: SplitDirection::Right,
25612                    focus: false,
25613                    clone: true,
25614                },
25615                window,
25616                cx,
25617            );
25618        })
25619        .unwrap();
25620    cx.run_until_parked();
25621    workspace
25622        .update(cx, |workspace, _, cx| {
25623            let panes = workspace.panes();
25624            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
25625            for pane in panes {
25626                let editor = pane
25627                    .read(cx)
25628                    .active_item()
25629                    .and_then(|item| item.downcast::<Editor>())
25630                    .expect("Should have opened an editor in each split");
25631                let editor_file = editor
25632                    .read(cx)
25633                    .buffer()
25634                    .read(cx)
25635                    .as_singleton()
25636                    .expect("test deals with singleton buffers")
25637                    .read(cx)
25638                    .file()
25639                    .expect("test buffese should have a file")
25640                    .path();
25641                assert_eq!(
25642                    editor_file.as_ref(),
25643                    rel_path("first.rs"),
25644                    "Both editors should be opened for the same file"
25645                )
25646            }
25647        })
25648        .unwrap();
25649
25650    cx.executor().advance_clock(Duration::from_millis(500));
25651    let save = editor.update_in(cx, |editor, window, cx| {
25652        editor.move_to_end(&MoveToEnd, window, cx);
25653        editor.handle_input("dirty", window, cx);
25654        editor.save(
25655            SaveOptions {
25656                format: true,
25657                autosave: true,
25658            },
25659            project.clone(),
25660            window,
25661            cx,
25662        )
25663    });
25664    save.await.unwrap();
25665
25666    color_request_handle.next().await.unwrap();
25667    cx.run_until_parked();
25668    assert_eq!(
25669        3,
25670        requests_made.load(atomic::Ordering::Acquire),
25671        "Should query for colors once per save and once per formatting after save"
25672    );
25673
25674    drop(editor);
25675    let close = workspace
25676        .update(cx, |workspace, window, cx| {
25677            workspace.active_pane().update(cx, |pane, cx| {
25678                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25679            })
25680        })
25681        .unwrap();
25682    close.await.unwrap();
25683    let close = workspace
25684        .update(cx, |workspace, window, cx| {
25685            workspace.active_pane().update(cx, |pane, cx| {
25686                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25687            })
25688        })
25689        .unwrap();
25690    close.await.unwrap();
25691    assert_eq!(
25692        3,
25693        requests_made.load(atomic::Ordering::Acquire),
25694        "After saving and closing all editors, no extra requests should be made"
25695    );
25696    workspace
25697        .update(cx, |workspace, _, cx| {
25698            assert!(
25699                workspace.active_item(cx).is_none(),
25700                "Should close all editors"
25701            )
25702        })
25703        .unwrap();
25704
25705    workspace
25706        .update(cx, |workspace, window, cx| {
25707            workspace.active_pane().update(cx, |pane, cx| {
25708                pane.navigate_backward(&workspace::GoBack, window, cx);
25709            })
25710        })
25711        .unwrap();
25712    cx.executor().advance_clock(Duration::from_millis(100));
25713    cx.run_until_parked();
25714    let editor = workspace
25715        .update(cx, |workspace, _, cx| {
25716            workspace
25717                .active_item(cx)
25718                .expect("Should have reopened the editor again after navigating back")
25719                .downcast::<Editor>()
25720                .expect("Should be an editor")
25721        })
25722        .unwrap();
25723    color_request_handle.next().await.unwrap();
25724    assert_eq!(
25725        3,
25726        requests_made.load(atomic::Ordering::Acquire),
25727        "Cache should be reused on buffer close and reopen"
25728    );
25729    editor.update(cx, |editor, cx| {
25730        assert_eq!(
25731            vec![expected_color],
25732            extract_color_inlays(editor, cx),
25733            "Should have an initial inlay"
25734        );
25735    });
25736
25737    drop(color_request_handle);
25738    let closure_requests_made = Arc::clone(&requests_made);
25739    let mut empty_color_request_handle = fake_language_server
25740        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25741            let requests_made = Arc::clone(&closure_requests_made);
25742            async move {
25743                assert_eq!(
25744                    params.text_document.uri,
25745                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25746                );
25747                requests_made.fetch_add(1, atomic::Ordering::Release);
25748                Ok(Vec::new())
25749            }
25750        });
25751    let save = editor.update_in(cx, |editor, window, cx| {
25752        editor.move_to_end(&MoveToEnd, window, cx);
25753        editor.handle_input("dirty_again", window, cx);
25754        editor.save(
25755            SaveOptions {
25756                format: false,
25757                autosave: true,
25758            },
25759            project.clone(),
25760            window,
25761            cx,
25762        )
25763    });
25764    save.await.unwrap();
25765
25766    empty_color_request_handle.next().await.unwrap();
25767    cx.run_until_parked();
25768    assert_eq!(
25769        4,
25770        requests_made.load(atomic::Ordering::Acquire),
25771        "Should query for colors once per save only, as formatting was not requested"
25772    );
25773    editor.update(cx, |editor, cx| {
25774        assert_eq!(
25775            Vec::<Rgba>::new(),
25776            extract_color_inlays(editor, cx),
25777            "Should clear all colors when the server returns an empty response"
25778        );
25779    });
25780}
25781
25782#[gpui::test]
25783async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
25784    init_test(cx, |_| {});
25785    let (editor, cx) = cx.add_window_view(Editor::single_line);
25786    editor.update_in(cx, |editor, window, cx| {
25787        editor.set_text("oops\n\nwow\n", window, cx)
25788    });
25789    cx.run_until_parked();
25790    editor.update(cx, |editor, cx| {
25791        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
25792    });
25793    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
25794    cx.run_until_parked();
25795    editor.update(cx, |editor, cx| {
25796        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
25797    });
25798}
25799
25800#[gpui::test]
25801async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
25802    init_test(cx, |_| {});
25803
25804    cx.update(|cx| {
25805        register_project_item::<Editor>(cx);
25806    });
25807
25808    let fs = FakeFs::new(cx.executor());
25809    fs.insert_tree("/root1", json!({})).await;
25810    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
25811        .await;
25812
25813    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
25814    let (workspace, cx) =
25815        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25816
25817    let worktree_id = project.update(cx, |project, cx| {
25818        project.worktrees(cx).next().unwrap().read(cx).id()
25819    });
25820
25821    let handle = workspace
25822        .update_in(cx, |workspace, window, cx| {
25823            let project_path = (worktree_id, rel_path("one.pdf"));
25824            workspace.open_path(project_path, None, true, window, cx)
25825        })
25826        .await
25827        .unwrap();
25828
25829    assert_eq!(
25830        handle.to_any().entity_type(),
25831        TypeId::of::<InvalidBufferView>()
25832    );
25833}
25834
25835#[gpui::test]
25836async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
25837    init_test(cx, |_| {});
25838
25839    let language = Arc::new(Language::new(
25840        LanguageConfig::default(),
25841        Some(tree_sitter_rust::LANGUAGE.into()),
25842    ));
25843
25844    // Test hierarchical sibling navigation
25845    let text = r#"
25846        fn outer() {
25847            if condition {
25848                let a = 1;
25849            }
25850            let b = 2;
25851        }
25852
25853        fn another() {
25854            let c = 3;
25855        }
25856    "#;
25857
25858    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
25859    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25860    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
25861
25862    // Wait for parsing to complete
25863    editor
25864        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
25865        .await;
25866
25867    editor.update_in(cx, |editor, window, cx| {
25868        // Start by selecting "let a = 1;" inside the if block
25869        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25870            s.select_display_ranges([
25871                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
25872            ]);
25873        });
25874
25875        let initial_selection = editor.selections.display_ranges(cx);
25876        assert_eq!(initial_selection.len(), 1, "Should have one selection");
25877
25878        // Test select next sibling - should move up levels to find the next sibling
25879        // Since "let a = 1;" has no siblings in the if block, it should move up
25880        // to find "let b = 2;" which is a sibling of the if block
25881        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
25882        let next_selection = editor.selections.display_ranges(cx);
25883
25884        // Should have a selection and it should be different from the initial
25885        assert_eq!(
25886            next_selection.len(),
25887            1,
25888            "Should have one selection after next"
25889        );
25890        assert_ne!(
25891            next_selection[0], initial_selection[0],
25892            "Next sibling selection should be different"
25893        );
25894
25895        // Test hierarchical navigation by going to the end of the current function
25896        // and trying to navigate to the next function
25897        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25898            s.select_display_ranges([
25899                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
25900            ]);
25901        });
25902
25903        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
25904        let function_next_selection = editor.selections.display_ranges(cx);
25905
25906        // Should move to the next function
25907        assert_eq!(
25908            function_next_selection.len(),
25909            1,
25910            "Should have one selection after function next"
25911        );
25912
25913        // Test select previous sibling navigation
25914        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
25915        let prev_selection = editor.selections.display_ranges(cx);
25916
25917        // Should have a selection and it should be different
25918        assert_eq!(
25919            prev_selection.len(),
25920            1,
25921            "Should have one selection after prev"
25922        );
25923        assert_ne!(
25924            prev_selection[0], function_next_selection[0],
25925            "Previous sibling selection should be different from next"
25926        );
25927    });
25928}
25929
25930#[gpui::test]
25931async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
25932    init_test(cx, |_| {});
25933
25934    let mut cx = EditorTestContext::new(cx).await;
25935    cx.set_state(
25936        "let ˇvariable = 42;
25937let another = variable + 1;
25938let result = variable * 2;",
25939    );
25940
25941    // Set up document highlights manually (simulating LSP response)
25942    cx.update_editor(|editor, _window, cx| {
25943        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
25944
25945        // Create highlights for "variable" occurrences
25946        let highlight_ranges = [
25947            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
25948            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
25949            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
25950        ];
25951
25952        let anchor_ranges: Vec<_> = highlight_ranges
25953            .iter()
25954            .map(|range| range.clone().to_anchors(&buffer_snapshot))
25955            .collect();
25956
25957        editor.highlight_background::<DocumentHighlightRead>(
25958            &anchor_ranges,
25959            |theme| theme.colors().editor_document_highlight_read_background,
25960            cx,
25961        );
25962    });
25963
25964    // Go to next highlight - should move to second "variable"
25965    cx.update_editor(|editor, window, cx| {
25966        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
25967    });
25968    cx.assert_editor_state(
25969        "let variable = 42;
25970let another = ˇvariable + 1;
25971let result = variable * 2;",
25972    );
25973
25974    // Go to next highlight - should move to third "variable"
25975    cx.update_editor(|editor, window, cx| {
25976        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
25977    });
25978    cx.assert_editor_state(
25979        "let variable = 42;
25980let another = variable + 1;
25981let result = ˇvariable * 2;",
25982    );
25983
25984    // Go to next highlight - should stay at third "variable" (no wrap-around)
25985    cx.update_editor(|editor, window, cx| {
25986        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
25987    });
25988    cx.assert_editor_state(
25989        "let variable = 42;
25990let another = variable + 1;
25991let result = ˇvariable * 2;",
25992    );
25993
25994    // Now test going backwards from third position
25995    cx.update_editor(|editor, window, cx| {
25996        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
25997    });
25998    cx.assert_editor_state(
25999        "let variable = 42;
26000let another = ˇvariable + 1;
26001let result = variable * 2;",
26002    );
26003
26004    // Go to previous highlight - should move to first "variable"
26005    cx.update_editor(|editor, window, cx| {
26006        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26007    });
26008    cx.assert_editor_state(
26009        "let ˇvariable = 42;
26010let another = variable + 1;
26011let result = variable * 2;",
26012    );
26013
26014    // Go to previous highlight - should stay on first "variable"
26015    cx.update_editor(|editor, window, cx| {
26016        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26017    });
26018    cx.assert_editor_state(
26019        "let ˇvariable = 42;
26020let another = variable + 1;
26021let result = variable * 2;",
26022    );
26023}
26024
26025#[gpui::test]
26026async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
26027    cx: &mut gpui::TestAppContext,
26028) {
26029    init_test(cx, |_| {});
26030
26031    let url = "https://zed.dev";
26032
26033    let markdown_language = Arc::new(Language::new(
26034        LanguageConfig {
26035            name: "Markdown".into(),
26036            ..LanguageConfig::default()
26037        },
26038        None,
26039    ));
26040
26041    let mut cx = EditorTestContext::new(cx).await;
26042    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26043    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
26044
26045    cx.update_editor(|editor, window, cx| {
26046        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26047        editor.paste(&Paste, window, cx);
26048    });
26049
26050    cx.assert_editor_state(&format!(
26051        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
26052    ));
26053}
26054
26055#[gpui::test]
26056async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
26057    cx: &mut gpui::TestAppContext,
26058) {
26059    init_test(cx, |_| {});
26060
26061    let url = "https://zed.dev";
26062
26063    let markdown_language = Arc::new(Language::new(
26064        LanguageConfig {
26065            name: "Markdown".into(),
26066            ..LanguageConfig::default()
26067        },
26068        None,
26069    ));
26070
26071    let mut cx = EditorTestContext::new(cx).await;
26072    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26073    cx.set_state(&format!(
26074        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
26075    ));
26076
26077    cx.update_editor(|editor, window, cx| {
26078        editor.copy(&Copy, window, cx);
26079    });
26080
26081    cx.set_state(&format!(
26082        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
26083    ));
26084
26085    cx.update_editor(|editor, window, cx| {
26086        editor.paste(&Paste, window, cx);
26087    });
26088
26089    cx.assert_editor_state(&format!(
26090        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
26091    ));
26092}
26093
26094#[gpui::test]
26095async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
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("Please visit zed's homepage: «https://www.apple.comˇ»");
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!("Please visit zed's homepage: {url}ˇ"));
26120}
26121
26122#[gpui::test]
26123async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
26124    cx: &mut gpui::TestAppContext,
26125) {
26126    init_test(cx, |_| {});
26127
26128    let text = "Awesome";
26129
26130    let markdown_language = Arc::new(Language::new(
26131        LanguageConfig {
26132            name: "Markdown".into(),
26133            ..LanguageConfig::default()
26134        },
26135        None,
26136    ));
26137
26138    let mut cx = EditorTestContext::new(cx).await;
26139    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26140    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
26141
26142    cx.update_editor(|editor, window, cx| {
26143        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
26144        editor.paste(&Paste, window, cx);
26145    });
26146
26147    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
26148}
26149
26150#[gpui::test]
26151async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
26152    cx: &mut gpui::TestAppContext,
26153) {
26154    init_test(cx, |_| {});
26155
26156    let url = "https://zed.dev";
26157
26158    let markdown_language = Arc::new(Language::new(
26159        LanguageConfig {
26160            name: "Rust".into(),
26161            ..LanguageConfig::default()
26162        },
26163        None,
26164    ));
26165
26166    let mut cx = EditorTestContext::new(cx).await;
26167    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26168    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
26169
26170    cx.update_editor(|editor, window, cx| {
26171        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26172        editor.paste(&Paste, window, cx);
26173    });
26174
26175    cx.assert_editor_state(&format!(
26176        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
26177    ));
26178}
26179
26180#[gpui::test]
26181async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
26182    cx: &mut TestAppContext,
26183) {
26184    init_test(cx, |_| {});
26185
26186    let url = "https://zed.dev";
26187
26188    let markdown_language = Arc::new(Language::new(
26189        LanguageConfig {
26190            name: "Markdown".into(),
26191            ..LanguageConfig::default()
26192        },
26193        None,
26194    ));
26195
26196    let (editor, cx) = cx.add_window_view(|window, cx| {
26197        let multi_buffer = MultiBuffer::build_multi(
26198            [
26199                ("this will embed -> link", vec![Point::row_range(0..1)]),
26200                ("this will replace -> link", vec![Point::row_range(0..1)]),
26201            ],
26202            cx,
26203        );
26204        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
26205        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26206            s.select_ranges(vec![
26207                Point::new(0, 19)..Point::new(0, 23),
26208                Point::new(1, 21)..Point::new(1, 25),
26209            ])
26210        });
26211        let first_buffer_id = multi_buffer
26212            .read(cx)
26213            .excerpt_buffer_ids()
26214            .into_iter()
26215            .next()
26216            .unwrap();
26217        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
26218        first_buffer.update(cx, |buffer, cx| {
26219            buffer.set_language(Some(markdown_language.clone()), cx);
26220        });
26221
26222        editor
26223    });
26224    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
26225
26226    cx.update_editor(|editor, window, cx| {
26227        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26228        editor.paste(&Paste, window, cx);
26229    });
26230
26231    cx.assert_editor_state(&format!(
26232        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
26233    ));
26234}
26235
26236#[track_caller]
26237fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
26238    editor
26239        .all_inlays(cx)
26240        .into_iter()
26241        .filter_map(|inlay| inlay.get_color())
26242        .map(Rgba::from)
26243        .collect()
26244}