editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    edit_prediction_tests::FakeEditPredictionProvider,
    6    linked_editing_ranges::LinkedEditingRanges,
    7    scroll::scroll_amount::ScrollAmount,
    8    test::{
    9        assert_text_with_selections, build_editor,
   10        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   11        editor_test_context::EditorTestContext,
   12        select_ranges,
   13    },
   14};
   15use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   16use collections::HashMap;
   17use futures::StreamExt;
   18use gpui::{
   19    BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
   20    VisualTestContext, WindowBounds, WindowOptions, div,
   21};
   22use indoc::indoc;
   23use language::{
   24    BracketPairConfig,
   25    Capability::ReadWrite,
   26    DiagnosticSourceKind, FakeLspAdapter, IndentGuideSettings, LanguageConfig,
   27    LanguageConfigOverride, LanguageMatcher, LanguageName, Override, Point,
   28    language_settings::{
   29        CompletionSettingsContent, FormatterList, LanguageSettingsContent, LspInsertMode,
   30        SelectedFormatter,
   31    },
   32    tree_sitter_python,
   33};
   34use language_settings::Formatter;
   35use lsp::CompletionParams;
   36use multi_buffer::{IndentGuide, PathKey};
   37use parking_lot::Mutex;
   38use pretty_assertions::{assert_eq, assert_ne};
   39use project::{
   40    FakeFs,
   41    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   42    project_settings::LspSettings,
   43};
   44use serde_json::{self, json};
   45use settings::{
   46    AllLanguageSettingsContent, IndentGuideBackgroundColoring, IndentGuideColoring,
   47    ProjectSettingsContent,
   48};
   49use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   50use std::{
   51    iter,
   52    sync::atomic::{self, AtomicUsize},
   53};
   54use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
   55use text::ToPoint as _;
   56use unindent::Unindent;
   57use util::{
   58    assert_set_eq, path,
   59    rel_path::rel_path,
   60    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   61    uri,
   62};
   63use workspace::{
   64    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   65    OpenOptions, ViewId,
   66    invalid_buffer_view::InvalidBufferView,
   67    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   68    register_project_item,
   69};
   70
   71#[gpui::test]
   72fn test_edit_events(cx: &mut TestAppContext) {
   73    init_test(cx, |_| {});
   74
   75    let buffer = cx.new(|cx| {
   76        let mut buffer = language::Buffer::local("123456", cx);
   77        buffer.set_group_interval(Duration::from_secs(1));
   78        buffer
   79    });
   80
   81    let events = Rc::new(RefCell::new(Vec::new()));
   82    let editor1 = cx.add_window({
   83        let events = events.clone();
   84        |window, cx| {
   85            let entity = cx.entity();
   86            cx.subscribe_in(
   87                &entity,
   88                window,
   89                move |_, _, event: &EditorEvent, _, _| match event {
   90                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   91                    EditorEvent::BufferEdited => {
   92                        events.borrow_mut().push(("editor1", "buffer edited"))
   93                    }
   94                    _ => {}
   95                },
   96            )
   97            .detach();
   98            Editor::for_buffer(buffer.clone(), None, window, cx)
   99        }
  100    });
  101
  102    let editor2 = cx.add_window({
  103        let events = events.clone();
  104        |window, cx| {
  105            cx.subscribe_in(
  106                &cx.entity(),
  107                window,
  108                move |_, _, event: &EditorEvent, _, _| match event {
  109                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  110                    EditorEvent::BufferEdited => {
  111                        events.borrow_mut().push(("editor2", "buffer edited"))
  112                    }
  113                    _ => {}
  114                },
  115            )
  116            .detach();
  117            Editor::for_buffer(buffer.clone(), None, window, cx)
  118        }
  119    });
  120
  121    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  122
  123    // Mutating editor 1 will emit an `Edited` event only for that editor.
  124    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  125    assert_eq!(
  126        mem::take(&mut *events.borrow_mut()),
  127        [
  128            ("editor1", "edited"),
  129            ("editor1", "buffer edited"),
  130            ("editor2", "buffer edited"),
  131        ]
  132    );
  133
  134    // Mutating editor 2 will emit an `Edited` event only for that editor.
  135    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  136    assert_eq!(
  137        mem::take(&mut *events.borrow_mut()),
  138        [
  139            ("editor2", "edited"),
  140            ("editor1", "buffer edited"),
  141            ("editor2", "buffer edited"),
  142        ]
  143    );
  144
  145    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  146    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  147    assert_eq!(
  148        mem::take(&mut *events.borrow_mut()),
  149        [
  150            ("editor1", "edited"),
  151            ("editor1", "buffer edited"),
  152            ("editor2", "buffer edited"),
  153        ]
  154    );
  155
  156    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  157    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  158    assert_eq!(
  159        mem::take(&mut *events.borrow_mut()),
  160        [
  161            ("editor1", "edited"),
  162            ("editor1", "buffer edited"),
  163            ("editor2", "buffer edited"),
  164        ]
  165    );
  166
  167    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  168    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  169    assert_eq!(
  170        mem::take(&mut *events.borrow_mut()),
  171        [
  172            ("editor2", "edited"),
  173            ("editor1", "buffer edited"),
  174            ("editor2", "buffer edited"),
  175        ]
  176    );
  177
  178    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  179    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  180    assert_eq!(
  181        mem::take(&mut *events.borrow_mut()),
  182        [
  183            ("editor2", "edited"),
  184            ("editor1", "buffer edited"),
  185            ("editor2", "buffer edited"),
  186        ]
  187    );
  188
  189    // No event is emitted when the mutation is a no-op.
  190    _ = editor2.update(cx, |editor, window, cx| {
  191        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  192            s.select_ranges([0..0])
  193        });
  194
  195        editor.backspace(&Backspace, window, cx);
  196    });
  197    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  198}
  199
  200#[gpui::test]
  201fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  202    init_test(cx, |_| {});
  203
  204    let mut now = Instant::now();
  205    let group_interval = Duration::from_millis(1);
  206    let buffer = cx.new(|cx| {
  207        let mut buf = language::Buffer::local("123456", cx);
  208        buf.set_group_interval(group_interval);
  209        buf
  210    });
  211    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  212    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  213
  214    _ = editor.update(cx, |editor, window, cx| {
  215        editor.start_transaction_at(now, window, cx);
  216        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  217            s.select_ranges([2..4])
  218        });
  219
  220        editor.insert("cd", window, cx);
  221        editor.end_transaction_at(now, cx);
  222        assert_eq!(editor.text(cx), "12cd56");
  223        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  224
  225        editor.start_transaction_at(now, window, cx);
  226        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  227            s.select_ranges([4..5])
  228        });
  229        editor.insert("e", window, cx);
  230        editor.end_transaction_at(now, cx);
  231        assert_eq!(editor.text(cx), "12cde6");
  232        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  233
  234        now += group_interval + Duration::from_millis(1);
  235        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  236            s.select_ranges([2..2])
  237        });
  238
  239        // Simulate an edit in another editor
  240        buffer.update(cx, |buffer, cx| {
  241            buffer.start_transaction_at(now, cx);
  242            buffer.edit([(0..1, "a")], None, cx);
  243            buffer.edit([(1..1, "b")], None, cx);
  244            buffer.end_transaction_at(now, cx);
  245        });
  246
  247        assert_eq!(editor.text(cx), "ab2cde6");
  248        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  249
  250        // Last transaction happened past the group interval in a different editor.
  251        // Undo it individually and don't restore selections.
  252        editor.undo(&Undo, window, cx);
  253        assert_eq!(editor.text(cx), "12cde6");
  254        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  255
  256        // First two transactions happened within the group interval in this editor.
  257        // Undo them together and restore selections.
  258        editor.undo(&Undo, window, cx);
  259        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  260        assert_eq!(editor.text(cx), "123456");
  261        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  262
  263        // Redo the first two transactions together.
  264        editor.redo(&Redo, window, cx);
  265        assert_eq!(editor.text(cx), "12cde6");
  266        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  267
  268        // Redo the last transaction on its own.
  269        editor.redo(&Redo, window, cx);
  270        assert_eq!(editor.text(cx), "ab2cde6");
  271        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  272
  273        // Test empty transactions.
  274        editor.start_transaction_at(now, window, cx);
  275        editor.end_transaction_at(now, cx);
  276        editor.undo(&Undo, window, cx);
  277        assert_eq!(editor.text(cx), "12cde6");
  278    });
  279}
  280
  281#[gpui::test]
  282fn test_ime_composition(cx: &mut TestAppContext) {
  283    init_test(cx, |_| {});
  284
  285    let buffer = cx.new(|cx| {
  286        let mut buffer = language::Buffer::local("abcde", cx);
  287        // Ensure automatic grouping doesn't occur.
  288        buffer.set_group_interval(Duration::ZERO);
  289        buffer
  290    });
  291
  292    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  293    cx.add_window(|window, cx| {
  294        let mut editor = build_editor(buffer.clone(), window, cx);
  295
  296        // Start a new IME composition.
  297        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  298        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  299        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  300        assert_eq!(editor.text(cx), "äbcde");
  301        assert_eq!(
  302            editor.marked_text_ranges(cx),
  303            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  304        );
  305
  306        // Finalize IME composition.
  307        editor.replace_text_in_range(None, "ā", window, cx);
  308        assert_eq!(editor.text(cx), "ābcde");
  309        assert_eq!(editor.marked_text_ranges(cx), None);
  310
  311        // IME composition edits are grouped and are undone/redone at once.
  312        editor.undo(&Default::default(), window, cx);
  313        assert_eq!(editor.text(cx), "abcde");
  314        assert_eq!(editor.marked_text_ranges(cx), None);
  315        editor.redo(&Default::default(), window, cx);
  316        assert_eq!(editor.text(cx), "ābcde");
  317        assert_eq!(editor.marked_text_ranges(cx), None);
  318
  319        // Start a new IME composition.
  320        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  321        assert_eq!(
  322            editor.marked_text_ranges(cx),
  323            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  324        );
  325
  326        // Undoing during an IME composition cancels it.
  327        editor.undo(&Default::default(), window, cx);
  328        assert_eq!(editor.text(cx), "ābcde");
  329        assert_eq!(editor.marked_text_ranges(cx), None);
  330
  331        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  332        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  333        assert_eq!(editor.text(cx), "ābcdè");
  334        assert_eq!(
  335            editor.marked_text_ranges(cx),
  336            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  337        );
  338
  339        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  340        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  341        assert_eq!(editor.text(cx), "ābcdę");
  342        assert_eq!(editor.marked_text_ranges(cx), None);
  343
  344        // Start a new IME composition with multiple cursors.
  345        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  346            s.select_ranges([
  347                OffsetUtf16(1)..OffsetUtf16(1),
  348                OffsetUtf16(3)..OffsetUtf16(3),
  349                OffsetUtf16(5)..OffsetUtf16(5),
  350            ])
  351        });
  352        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  353        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  354        assert_eq!(
  355            editor.marked_text_ranges(cx),
  356            Some(vec![
  357                OffsetUtf16(0)..OffsetUtf16(3),
  358                OffsetUtf16(4)..OffsetUtf16(7),
  359                OffsetUtf16(8)..OffsetUtf16(11)
  360            ])
  361        );
  362
  363        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  364        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  365        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  366        assert_eq!(
  367            editor.marked_text_ranges(cx),
  368            Some(vec![
  369                OffsetUtf16(1)..OffsetUtf16(2),
  370                OffsetUtf16(5)..OffsetUtf16(6),
  371                OffsetUtf16(9)..OffsetUtf16(10)
  372            ])
  373        );
  374
  375        // Finalize IME composition with multiple cursors.
  376        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  377        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  378        assert_eq!(editor.marked_text_ranges(cx), None);
  379
  380        editor
  381    });
  382}
  383
  384#[gpui::test]
  385fn test_selection_with_mouse(cx: &mut TestAppContext) {
  386    init_test(cx, |_| {});
  387
  388    let editor = cx.add_window(|window, cx| {
  389        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  390        build_editor(buffer, window, cx)
  391    });
  392
  393    _ = editor.update(cx, |editor, window, cx| {
  394        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  395    });
  396    assert_eq!(
  397        editor
  398            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  399            .unwrap(),
  400        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  401    );
  402
  403    _ = editor.update(cx, |editor, window, cx| {
  404        editor.update_selection(
  405            DisplayPoint::new(DisplayRow(3), 3),
  406            0,
  407            gpui::Point::<f32>::default(),
  408            window,
  409            cx,
  410        );
  411    });
  412
  413    assert_eq!(
  414        editor
  415            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  416            .unwrap(),
  417        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  418    );
  419
  420    _ = editor.update(cx, |editor, window, cx| {
  421        editor.update_selection(
  422            DisplayPoint::new(DisplayRow(1), 1),
  423            0,
  424            gpui::Point::<f32>::default(),
  425            window,
  426            cx,
  427        );
  428    });
  429
  430    assert_eq!(
  431        editor
  432            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  433            .unwrap(),
  434        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  435    );
  436
  437    _ = editor.update(cx, |editor, window, cx| {
  438        editor.end_selection(window, cx);
  439        editor.update_selection(
  440            DisplayPoint::new(DisplayRow(3), 3),
  441            0,
  442            gpui::Point::<f32>::default(),
  443            window,
  444            cx,
  445        );
  446    });
  447
  448    assert_eq!(
  449        editor
  450            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  451            .unwrap(),
  452        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  453    );
  454
  455    _ = editor.update(cx, |editor, window, cx| {
  456        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  457        editor.update_selection(
  458            DisplayPoint::new(DisplayRow(0), 0),
  459            0,
  460            gpui::Point::<f32>::default(),
  461            window,
  462            cx,
  463        );
  464    });
  465
  466    assert_eq!(
  467        editor
  468            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  469            .unwrap(),
  470        [
  471            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  472            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  473        ]
  474    );
  475
  476    _ = editor.update(cx, |editor, window, cx| {
  477        editor.end_selection(window, cx);
  478    });
  479
  480    assert_eq!(
  481        editor
  482            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  483            .unwrap(),
  484        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  485    );
  486}
  487
  488#[gpui::test]
  489fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  490    init_test(cx, |_| {});
  491
  492    let editor = cx.add_window(|window, cx| {
  493        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  494        build_editor(buffer, window, cx)
  495    });
  496
  497    _ = editor.update(cx, |editor, window, cx| {
  498        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  499    });
  500
  501    _ = editor.update(cx, |editor, window, cx| {
  502        editor.end_selection(window, cx);
  503    });
  504
  505    _ = editor.update(cx, |editor, window, cx| {
  506        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  507    });
  508
  509    _ = editor.update(cx, |editor, window, cx| {
  510        editor.end_selection(window, cx);
  511    });
  512
  513    assert_eq!(
  514        editor
  515            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  516            .unwrap(),
  517        [
  518            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  519            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  520        ]
  521    );
  522
  523    _ = editor.update(cx, |editor, window, cx| {
  524        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  525    });
  526
  527    _ = editor.update(cx, |editor, window, cx| {
  528        editor.end_selection(window, cx);
  529    });
  530
  531    assert_eq!(
  532        editor
  533            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  534            .unwrap(),
  535        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  536    );
  537}
  538
  539#[gpui::test]
  540fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  541    init_test(cx, |_| {});
  542
  543    let editor = cx.add_window(|window, cx| {
  544        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  545        build_editor(buffer, window, cx)
  546    });
  547
  548    _ = editor.update(cx, |editor, window, cx| {
  549        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  550        assert_eq!(
  551            editor.selections.display_ranges(cx),
  552            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  553        );
  554    });
  555
  556    _ = editor.update(cx, |editor, window, cx| {
  557        editor.update_selection(
  558            DisplayPoint::new(DisplayRow(3), 3),
  559            0,
  560            gpui::Point::<f32>::default(),
  561            window,
  562            cx,
  563        );
  564        assert_eq!(
  565            editor.selections.display_ranges(cx),
  566            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  567        );
  568    });
  569
  570    _ = editor.update(cx, |editor, window, cx| {
  571        editor.cancel(&Cancel, window, cx);
  572        editor.update_selection(
  573            DisplayPoint::new(DisplayRow(1), 1),
  574            0,
  575            gpui::Point::<f32>::default(),
  576            window,
  577            cx,
  578        );
  579        assert_eq!(
  580            editor.selections.display_ranges(cx),
  581            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  582        );
  583    });
  584}
  585
  586#[gpui::test]
  587fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  588    init_test(cx, |_| {});
  589
  590    let editor = cx.add_window(|window, cx| {
  591        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  592        build_editor(buffer, window, cx)
  593    });
  594
  595    _ = editor.update(cx, |editor, window, cx| {
  596        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  597        assert_eq!(
  598            editor.selections.display_ranges(cx),
  599            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  600        );
  601
  602        editor.move_down(&Default::default(), window, cx);
  603        assert_eq!(
  604            editor.selections.display_ranges(cx),
  605            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  606        );
  607
  608        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  609        assert_eq!(
  610            editor.selections.display_ranges(cx),
  611            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  612        );
  613
  614        editor.move_up(&Default::default(), window, cx);
  615        assert_eq!(
  616            editor.selections.display_ranges(cx),
  617            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  618        );
  619    });
  620}
  621
  622#[gpui::test]
  623fn test_clone(cx: &mut TestAppContext) {
  624    init_test(cx, |_| {});
  625
  626    let (text, selection_ranges) = marked_text_ranges(
  627        indoc! {"
  628            one
  629            two
  630            threeˇ
  631            four
  632            fiveˇ
  633        "},
  634        true,
  635    );
  636
  637    let editor = cx.add_window(|window, cx| {
  638        let buffer = MultiBuffer::build_simple(&text, cx);
  639        build_editor(buffer, window, cx)
  640    });
  641
  642    _ = editor.update(cx, |editor, window, cx| {
  643        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  644            s.select_ranges(selection_ranges.clone())
  645        });
  646        editor.fold_creases(
  647            vec![
  648                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  649                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  650            ],
  651            true,
  652            window,
  653            cx,
  654        );
  655    });
  656
  657    let cloned_editor = editor
  658        .update(cx, |editor, _, cx| {
  659            cx.open_window(Default::default(), |window, cx| {
  660                cx.new(|cx| editor.clone(window, cx))
  661            })
  662        })
  663        .unwrap()
  664        .unwrap();
  665
  666    let snapshot = editor
  667        .update(cx, |e, window, cx| e.snapshot(window, cx))
  668        .unwrap();
  669    let cloned_snapshot = cloned_editor
  670        .update(cx, |e, window, cx| e.snapshot(window, cx))
  671        .unwrap();
  672
  673    assert_eq!(
  674        cloned_editor
  675            .update(cx, |e, _, cx| e.display_text(cx))
  676            .unwrap(),
  677        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  678    );
  679    assert_eq!(
  680        cloned_snapshot
  681            .folds_in_range(0..text.len())
  682            .collect::<Vec<_>>(),
  683        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  684    );
  685    assert_set_eq!(
  686        cloned_editor
  687            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  688            .unwrap(),
  689        editor
  690            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  691            .unwrap()
  692    );
  693    assert_set_eq!(
  694        cloned_editor
  695            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  696            .unwrap(),
  697        editor
  698            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  699            .unwrap()
  700    );
  701}
  702
  703#[gpui::test]
  704async fn test_navigation_history(cx: &mut TestAppContext) {
  705    init_test(cx, |_| {});
  706
  707    use workspace::item::Item;
  708
  709    let fs = FakeFs::new(cx.executor());
  710    let project = Project::test(fs, [], cx).await;
  711    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  712    let pane = workspace
  713        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  714        .unwrap();
  715
  716    _ = workspace.update(cx, |_v, window, cx| {
  717        cx.new(|cx| {
  718            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  719            let mut editor = build_editor(buffer, window, cx);
  720            let handle = cx.entity();
  721            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  722
  723            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  724                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  725            }
  726
  727            // Move the cursor a small distance.
  728            // Nothing is added to the navigation history.
  729            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  730                s.select_display_ranges([
  731                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  732                ])
  733            });
  734            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  735                s.select_display_ranges([
  736                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  737                ])
  738            });
  739            assert!(pop_history(&mut editor, cx).is_none());
  740
  741            // Move the cursor a large distance.
  742            // The history can jump back to the previous position.
  743            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  744                s.select_display_ranges([
  745                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  746                ])
  747            });
  748            let nav_entry = pop_history(&mut editor, cx).unwrap();
  749            editor.navigate(nav_entry.data.unwrap(), window, cx);
  750            assert_eq!(nav_entry.item.id(), cx.entity_id());
  751            assert_eq!(
  752                editor.selections.display_ranges(cx),
  753                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  754            );
  755            assert!(pop_history(&mut editor, cx).is_none());
  756
  757            // Move the cursor a small distance via the mouse.
  758            // Nothing is added to the navigation history.
  759            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  760            editor.end_selection(window, cx);
  761            assert_eq!(
  762                editor.selections.display_ranges(cx),
  763                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  764            );
  765            assert!(pop_history(&mut editor, cx).is_none());
  766
  767            // Move the cursor a large distance via the mouse.
  768            // The history can jump back to the previous position.
  769            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  770            editor.end_selection(window, cx);
  771            assert_eq!(
  772                editor.selections.display_ranges(cx),
  773                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  774            );
  775            let nav_entry = pop_history(&mut editor, cx).unwrap();
  776            editor.navigate(nav_entry.data.unwrap(), window, cx);
  777            assert_eq!(nav_entry.item.id(), cx.entity_id());
  778            assert_eq!(
  779                editor.selections.display_ranges(cx),
  780                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  781            );
  782            assert!(pop_history(&mut editor, cx).is_none());
  783
  784            // Set scroll position to check later
  785            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
  786            let original_scroll_position = editor.scroll_manager.anchor();
  787
  788            // Jump to the end of the document and adjust scroll
  789            editor.move_to_end(&MoveToEnd, window, cx);
  790            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
  791            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  792
  793            let nav_entry = pop_history(&mut editor, cx).unwrap();
  794            editor.navigate(nav_entry.data.unwrap(), window, cx);
  795            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  796
  797            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  798            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  799            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  800            let invalid_point = Point::new(9999, 0);
  801            editor.navigate(
  802                Box::new(NavigationData {
  803                    cursor_anchor: invalid_anchor,
  804                    cursor_position: invalid_point,
  805                    scroll_anchor: ScrollAnchor {
  806                        anchor: invalid_anchor,
  807                        offset: Default::default(),
  808                    },
  809                    scroll_top_row: invalid_point.row,
  810                }),
  811                window,
  812                cx,
  813            );
  814            assert_eq!(
  815                editor.selections.display_ranges(cx),
  816                &[editor.max_point(cx)..editor.max_point(cx)]
  817            );
  818            assert_eq!(
  819                editor.scroll_position(cx),
  820                gpui::Point::new(0., editor.max_point(cx).row().as_f32())
  821            );
  822
  823            editor
  824        })
  825    });
  826}
  827
  828#[gpui::test]
  829fn test_cancel(cx: &mut TestAppContext) {
  830    init_test(cx, |_| {});
  831
  832    let editor = cx.add_window(|window, cx| {
  833        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  834        build_editor(buffer, window, cx)
  835    });
  836
  837    _ = editor.update(cx, |editor, window, cx| {
  838        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  839        editor.update_selection(
  840            DisplayPoint::new(DisplayRow(1), 1),
  841            0,
  842            gpui::Point::<f32>::default(),
  843            window,
  844            cx,
  845        );
  846        editor.end_selection(window, cx);
  847
  848        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  849        editor.update_selection(
  850            DisplayPoint::new(DisplayRow(0), 3),
  851            0,
  852            gpui::Point::<f32>::default(),
  853            window,
  854            cx,
  855        );
  856        editor.end_selection(window, cx);
  857        assert_eq!(
  858            editor.selections.display_ranges(cx),
  859            [
  860                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  861                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  862            ]
  863        );
  864    });
  865
  866    _ = editor.update(cx, |editor, window, cx| {
  867        editor.cancel(&Cancel, window, cx);
  868        assert_eq!(
  869            editor.selections.display_ranges(cx),
  870            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  871        );
  872    });
  873
  874    _ = editor.update(cx, |editor, window, cx| {
  875        editor.cancel(&Cancel, window, cx);
  876        assert_eq!(
  877            editor.selections.display_ranges(cx),
  878            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  879        );
  880    });
  881}
  882
  883#[gpui::test]
  884fn test_fold_action(cx: &mut TestAppContext) {
  885    init_test(cx, |_| {});
  886
  887    let editor = cx.add_window(|window, cx| {
  888        let buffer = MultiBuffer::build_simple(
  889            &"
  890                impl Foo {
  891                    // Hello!
  892
  893                    fn a() {
  894                        1
  895                    }
  896
  897                    fn b() {
  898                        2
  899                    }
  900
  901                    fn c() {
  902                        3
  903                    }
  904                }
  905            "
  906            .unindent(),
  907            cx,
  908        );
  909        build_editor(buffer, window, cx)
  910    });
  911
  912    _ = editor.update(cx, |editor, window, cx| {
  913        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  914            s.select_display_ranges([
  915                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  916            ]);
  917        });
  918        editor.fold(&Fold, window, cx);
  919        assert_eq!(
  920            editor.display_text(cx),
  921            "
  922                impl Foo {
  923                    // Hello!
  924
  925                    fn a() {
  926                        1
  927                    }
  928
  929                    fn b() {⋯
  930                    }
  931
  932                    fn c() {⋯
  933                    }
  934                }
  935            "
  936            .unindent(),
  937        );
  938
  939        editor.fold(&Fold, window, cx);
  940        assert_eq!(
  941            editor.display_text(cx),
  942            "
  943                impl Foo {⋯
  944                }
  945            "
  946            .unindent(),
  947        );
  948
  949        editor.unfold_lines(&UnfoldLines, window, cx);
  950        assert_eq!(
  951            editor.display_text(cx),
  952            "
  953                impl Foo {
  954                    // Hello!
  955
  956                    fn a() {
  957                        1
  958                    }
  959
  960                    fn b() {⋯
  961                    }
  962
  963                    fn c() {⋯
  964                    }
  965                }
  966            "
  967            .unindent(),
  968        );
  969
  970        editor.unfold_lines(&UnfoldLines, window, cx);
  971        assert_eq!(
  972            editor.display_text(cx),
  973            editor.buffer.read(cx).read(cx).text()
  974        );
  975    });
  976}
  977
  978#[gpui::test]
  979fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  980    init_test(cx, |_| {});
  981
  982    let editor = cx.add_window(|window, cx| {
  983        let buffer = MultiBuffer::build_simple(
  984            &"
  985                class Foo:
  986                    # Hello!
  987
  988                    def a():
  989                        print(1)
  990
  991                    def b():
  992                        print(2)
  993
  994                    def c():
  995                        print(3)
  996            "
  997            .unindent(),
  998            cx,
  999        );
 1000        build_editor(buffer, window, cx)
 1001    });
 1002
 1003    _ = editor.update(cx, |editor, window, cx| {
 1004        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1005            s.select_display_ranges([
 1006                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1007            ]);
 1008        });
 1009        editor.fold(&Fold, window, cx);
 1010        assert_eq!(
 1011            editor.display_text(cx),
 1012            "
 1013                class Foo:
 1014                    # Hello!
 1015
 1016                    def a():
 1017                        print(1)
 1018
 1019                    def b():⋯
 1020
 1021                    def c():⋯
 1022            "
 1023            .unindent(),
 1024        );
 1025
 1026        editor.fold(&Fold, window, cx);
 1027        assert_eq!(
 1028            editor.display_text(cx),
 1029            "
 1030                class Foo:⋯
 1031            "
 1032            .unindent(),
 1033        );
 1034
 1035        editor.unfold_lines(&UnfoldLines, window, cx);
 1036        assert_eq!(
 1037            editor.display_text(cx),
 1038            "
 1039                class Foo:
 1040                    # Hello!
 1041
 1042                    def a():
 1043                        print(1)
 1044
 1045                    def b():⋯
 1046
 1047                    def c():⋯
 1048            "
 1049            .unindent(),
 1050        );
 1051
 1052        editor.unfold_lines(&UnfoldLines, window, cx);
 1053        assert_eq!(
 1054            editor.display_text(cx),
 1055            editor.buffer.read(cx).read(cx).text()
 1056        );
 1057    });
 1058}
 1059
 1060#[gpui::test]
 1061fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1062    init_test(cx, |_| {});
 1063
 1064    let editor = cx.add_window(|window, cx| {
 1065        let buffer = MultiBuffer::build_simple(
 1066            &"
 1067                class Foo:
 1068                    # Hello!
 1069
 1070                    def a():
 1071                        print(1)
 1072
 1073                    def b():
 1074                        print(2)
 1075
 1076
 1077                    def c():
 1078                        print(3)
 1079
 1080
 1081            "
 1082            .unindent(),
 1083            cx,
 1084        );
 1085        build_editor(buffer, window, cx)
 1086    });
 1087
 1088    _ = editor.update(cx, |editor, window, cx| {
 1089        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1090            s.select_display_ranges([
 1091                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1092            ]);
 1093        });
 1094        editor.fold(&Fold, window, cx);
 1095        assert_eq!(
 1096            editor.display_text(cx),
 1097            "
 1098                class Foo:
 1099                    # Hello!
 1100
 1101                    def a():
 1102                        print(1)
 1103
 1104                    def b():⋯
 1105
 1106
 1107                    def c():⋯
 1108
 1109
 1110            "
 1111            .unindent(),
 1112        );
 1113
 1114        editor.fold(&Fold, window, cx);
 1115        assert_eq!(
 1116            editor.display_text(cx),
 1117            "
 1118                class Foo:⋯
 1119
 1120
 1121            "
 1122            .unindent(),
 1123        );
 1124
 1125        editor.unfold_lines(&UnfoldLines, window, cx);
 1126        assert_eq!(
 1127            editor.display_text(cx),
 1128            "
 1129                class Foo:
 1130                    # Hello!
 1131
 1132                    def a():
 1133                        print(1)
 1134
 1135                    def b():⋯
 1136
 1137
 1138                    def c():⋯
 1139
 1140
 1141            "
 1142            .unindent(),
 1143        );
 1144
 1145        editor.unfold_lines(&UnfoldLines, window, cx);
 1146        assert_eq!(
 1147            editor.display_text(cx),
 1148            editor.buffer.read(cx).read(cx).text()
 1149        );
 1150    });
 1151}
 1152
 1153#[gpui::test]
 1154fn test_fold_at_level(cx: &mut TestAppContext) {
 1155    init_test(cx, |_| {});
 1156
 1157    let editor = cx.add_window(|window, cx| {
 1158        let buffer = MultiBuffer::build_simple(
 1159            &"
 1160                class Foo:
 1161                    # Hello!
 1162
 1163                    def a():
 1164                        print(1)
 1165
 1166                    def b():
 1167                        print(2)
 1168
 1169
 1170                class Bar:
 1171                    # World!
 1172
 1173                    def a():
 1174                        print(1)
 1175
 1176                    def b():
 1177                        print(2)
 1178
 1179
 1180            "
 1181            .unindent(),
 1182            cx,
 1183        );
 1184        build_editor(buffer, window, cx)
 1185    });
 1186
 1187    _ = editor.update(cx, |editor, window, cx| {
 1188        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1189        assert_eq!(
 1190            editor.display_text(cx),
 1191            "
 1192                class Foo:
 1193                    # Hello!
 1194
 1195                    def a():⋯
 1196
 1197                    def b():⋯
 1198
 1199
 1200                class Bar:
 1201                    # World!
 1202
 1203                    def a():⋯
 1204
 1205                    def b():⋯
 1206
 1207
 1208            "
 1209            .unindent(),
 1210        );
 1211
 1212        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1213        assert_eq!(
 1214            editor.display_text(cx),
 1215            "
 1216                class Foo:⋯
 1217
 1218
 1219                class Bar:⋯
 1220
 1221
 1222            "
 1223            .unindent(),
 1224        );
 1225
 1226        editor.unfold_all(&UnfoldAll, window, cx);
 1227        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1228        assert_eq!(
 1229            editor.display_text(cx),
 1230            "
 1231                class Foo:
 1232                    # Hello!
 1233
 1234                    def a():
 1235                        print(1)
 1236
 1237                    def b():
 1238                        print(2)
 1239
 1240
 1241                class Bar:
 1242                    # World!
 1243
 1244                    def a():
 1245                        print(1)
 1246
 1247                    def b():
 1248                        print(2)
 1249
 1250
 1251            "
 1252            .unindent(),
 1253        );
 1254
 1255        assert_eq!(
 1256            editor.display_text(cx),
 1257            editor.buffer.read(cx).read(cx).text()
 1258        );
 1259    });
 1260}
 1261
 1262#[gpui::test]
 1263fn test_move_cursor(cx: &mut TestAppContext) {
 1264    init_test(cx, |_| {});
 1265
 1266    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1267    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1268
 1269    buffer.update(cx, |buffer, cx| {
 1270        buffer.edit(
 1271            vec![
 1272                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1273                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1274            ],
 1275            None,
 1276            cx,
 1277        );
 1278    });
 1279    _ = editor.update(cx, |editor, window, cx| {
 1280        assert_eq!(
 1281            editor.selections.display_ranges(cx),
 1282            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1283        );
 1284
 1285        editor.move_down(&MoveDown, window, cx);
 1286        assert_eq!(
 1287            editor.selections.display_ranges(cx),
 1288            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1289        );
 1290
 1291        editor.move_right(&MoveRight, window, cx);
 1292        assert_eq!(
 1293            editor.selections.display_ranges(cx),
 1294            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1295        );
 1296
 1297        editor.move_left(&MoveLeft, window, cx);
 1298        assert_eq!(
 1299            editor.selections.display_ranges(cx),
 1300            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1301        );
 1302
 1303        editor.move_up(&MoveUp, window, cx);
 1304        assert_eq!(
 1305            editor.selections.display_ranges(cx),
 1306            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1307        );
 1308
 1309        editor.move_to_end(&MoveToEnd, window, cx);
 1310        assert_eq!(
 1311            editor.selections.display_ranges(cx),
 1312            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1313        );
 1314
 1315        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1316        assert_eq!(
 1317            editor.selections.display_ranges(cx),
 1318            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1319        );
 1320
 1321        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1322            s.select_display_ranges([
 1323                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1324            ]);
 1325        });
 1326        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1327        assert_eq!(
 1328            editor.selections.display_ranges(cx),
 1329            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1330        );
 1331
 1332        editor.select_to_end(&SelectToEnd, window, cx);
 1333        assert_eq!(
 1334            editor.selections.display_ranges(cx),
 1335            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1336        );
 1337    });
 1338}
 1339
 1340#[gpui::test]
 1341fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1342    init_test(cx, |_| {});
 1343
 1344    let editor = cx.add_window(|window, cx| {
 1345        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1346        build_editor(buffer, window, cx)
 1347    });
 1348
 1349    assert_eq!('🟥'.len_utf8(), 4);
 1350    assert_eq!('α'.len_utf8(), 2);
 1351
 1352    _ = editor.update(cx, |editor, window, cx| {
 1353        editor.fold_creases(
 1354            vec![
 1355                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1356                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1357                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1358            ],
 1359            true,
 1360            window,
 1361            cx,
 1362        );
 1363        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1364
 1365        editor.move_right(&MoveRight, window, cx);
 1366        assert_eq!(
 1367            editor.selections.display_ranges(cx),
 1368            &[empty_range(0, "🟥".len())]
 1369        );
 1370        editor.move_right(&MoveRight, window, cx);
 1371        assert_eq!(
 1372            editor.selections.display_ranges(cx),
 1373            &[empty_range(0, "🟥🟧".len())]
 1374        );
 1375        editor.move_right(&MoveRight, window, cx);
 1376        assert_eq!(
 1377            editor.selections.display_ranges(cx),
 1378            &[empty_range(0, "🟥🟧⋯".len())]
 1379        );
 1380
 1381        editor.move_down(&MoveDown, window, cx);
 1382        assert_eq!(
 1383            editor.selections.display_ranges(cx),
 1384            &[empty_range(1, "ab⋯e".len())]
 1385        );
 1386        editor.move_left(&MoveLeft, window, cx);
 1387        assert_eq!(
 1388            editor.selections.display_ranges(cx),
 1389            &[empty_range(1, "ab⋯".len())]
 1390        );
 1391        editor.move_left(&MoveLeft, window, cx);
 1392        assert_eq!(
 1393            editor.selections.display_ranges(cx),
 1394            &[empty_range(1, "ab".len())]
 1395        );
 1396        editor.move_left(&MoveLeft, window, cx);
 1397        assert_eq!(
 1398            editor.selections.display_ranges(cx),
 1399            &[empty_range(1, "a".len())]
 1400        );
 1401
 1402        editor.move_down(&MoveDown, window, cx);
 1403        assert_eq!(
 1404            editor.selections.display_ranges(cx),
 1405            &[empty_range(2, "α".len())]
 1406        );
 1407        editor.move_right(&MoveRight, window, cx);
 1408        assert_eq!(
 1409            editor.selections.display_ranges(cx),
 1410            &[empty_range(2, "αβ".len())]
 1411        );
 1412        editor.move_right(&MoveRight, window, cx);
 1413        assert_eq!(
 1414            editor.selections.display_ranges(cx),
 1415            &[empty_range(2, "αβ⋯".len())]
 1416        );
 1417        editor.move_right(&MoveRight, window, cx);
 1418        assert_eq!(
 1419            editor.selections.display_ranges(cx),
 1420            &[empty_range(2, "αβ⋯ε".len())]
 1421        );
 1422
 1423        editor.move_up(&MoveUp, window, cx);
 1424        assert_eq!(
 1425            editor.selections.display_ranges(cx),
 1426            &[empty_range(1, "ab⋯e".len())]
 1427        );
 1428        editor.move_down(&MoveDown, window, cx);
 1429        assert_eq!(
 1430            editor.selections.display_ranges(cx),
 1431            &[empty_range(2, "αβ⋯ε".len())]
 1432        );
 1433        editor.move_up(&MoveUp, window, cx);
 1434        assert_eq!(
 1435            editor.selections.display_ranges(cx),
 1436            &[empty_range(1, "ab⋯e".len())]
 1437        );
 1438
 1439        editor.move_up(&MoveUp, window, cx);
 1440        assert_eq!(
 1441            editor.selections.display_ranges(cx),
 1442            &[empty_range(0, "🟥🟧".len())]
 1443        );
 1444        editor.move_left(&MoveLeft, window, cx);
 1445        assert_eq!(
 1446            editor.selections.display_ranges(cx),
 1447            &[empty_range(0, "🟥".len())]
 1448        );
 1449        editor.move_left(&MoveLeft, window, cx);
 1450        assert_eq!(
 1451            editor.selections.display_ranges(cx),
 1452            &[empty_range(0, "".len())]
 1453        );
 1454    });
 1455}
 1456
 1457#[gpui::test]
 1458fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1459    init_test(cx, |_| {});
 1460
 1461    let editor = cx.add_window(|window, cx| {
 1462        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1463        build_editor(buffer, window, cx)
 1464    });
 1465    _ = editor.update(cx, |editor, window, cx| {
 1466        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1467            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1468        });
 1469
 1470        // moving above start of document should move selection to start of document,
 1471        // but the next move down should still be at the original goal_x
 1472        editor.move_up(&MoveUp, window, cx);
 1473        assert_eq!(
 1474            editor.selections.display_ranges(cx),
 1475            &[empty_range(0, "".len())]
 1476        );
 1477
 1478        editor.move_down(&MoveDown, window, cx);
 1479        assert_eq!(
 1480            editor.selections.display_ranges(cx),
 1481            &[empty_range(1, "abcd".len())]
 1482        );
 1483
 1484        editor.move_down(&MoveDown, window, cx);
 1485        assert_eq!(
 1486            editor.selections.display_ranges(cx),
 1487            &[empty_range(2, "αβγ".len())]
 1488        );
 1489
 1490        editor.move_down(&MoveDown, window, cx);
 1491        assert_eq!(
 1492            editor.selections.display_ranges(cx),
 1493            &[empty_range(3, "abcd".len())]
 1494        );
 1495
 1496        editor.move_down(&MoveDown, window, cx);
 1497        assert_eq!(
 1498            editor.selections.display_ranges(cx),
 1499            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1500        );
 1501
 1502        // moving past end of document should not change goal_x
 1503        editor.move_down(&MoveDown, window, cx);
 1504        assert_eq!(
 1505            editor.selections.display_ranges(cx),
 1506            &[empty_range(5, "".len())]
 1507        );
 1508
 1509        editor.move_down(&MoveDown, window, cx);
 1510        assert_eq!(
 1511            editor.selections.display_ranges(cx),
 1512            &[empty_range(5, "".len())]
 1513        );
 1514
 1515        editor.move_up(&MoveUp, window, cx);
 1516        assert_eq!(
 1517            editor.selections.display_ranges(cx),
 1518            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1519        );
 1520
 1521        editor.move_up(&MoveUp, window, cx);
 1522        assert_eq!(
 1523            editor.selections.display_ranges(cx),
 1524            &[empty_range(3, "abcd".len())]
 1525        );
 1526
 1527        editor.move_up(&MoveUp, window, cx);
 1528        assert_eq!(
 1529            editor.selections.display_ranges(cx),
 1530            &[empty_range(2, "αβγ".len())]
 1531        );
 1532    });
 1533}
 1534
 1535#[gpui::test]
 1536fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1537    init_test(cx, |_| {});
 1538    let move_to_beg = MoveToBeginningOfLine {
 1539        stop_at_soft_wraps: true,
 1540        stop_at_indent: true,
 1541    };
 1542
 1543    let delete_to_beg = DeleteToBeginningOfLine {
 1544        stop_at_indent: false,
 1545    };
 1546
 1547    let move_to_end = MoveToEndOfLine {
 1548        stop_at_soft_wraps: true,
 1549    };
 1550
 1551    let editor = cx.add_window(|window, cx| {
 1552        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1553        build_editor(buffer, window, cx)
 1554    });
 1555    _ = editor.update(cx, |editor, window, cx| {
 1556        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1557            s.select_display_ranges([
 1558                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1559                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1560            ]);
 1561        });
 1562    });
 1563
 1564    _ = editor.update(cx, |editor, window, cx| {
 1565        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1566        assert_eq!(
 1567            editor.selections.display_ranges(cx),
 1568            &[
 1569                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1570                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1571            ]
 1572        );
 1573    });
 1574
 1575    _ = editor.update(cx, |editor, window, cx| {
 1576        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1577        assert_eq!(
 1578            editor.selections.display_ranges(cx),
 1579            &[
 1580                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1581                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1582            ]
 1583        );
 1584    });
 1585
 1586    _ = editor.update(cx, |editor, window, cx| {
 1587        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1588        assert_eq!(
 1589            editor.selections.display_ranges(cx),
 1590            &[
 1591                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1592                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1593            ]
 1594        );
 1595    });
 1596
 1597    _ = editor.update(cx, |editor, window, cx| {
 1598        editor.move_to_end_of_line(&move_to_end, window, cx);
 1599        assert_eq!(
 1600            editor.selections.display_ranges(cx),
 1601            &[
 1602                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1603                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1604            ]
 1605        );
 1606    });
 1607
 1608    // Moving to the end of line again is a no-op.
 1609    _ = editor.update(cx, |editor, window, cx| {
 1610        editor.move_to_end_of_line(&move_to_end, window, cx);
 1611        assert_eq!(
 1612            editor.selections.display_ranges(cx),
 1613            &[
 1614                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1615                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1616            ]
 1617        );
 1618    });
 1619
 1620    _ = editor.update(cx, |editor, window, cx| {
 1621        editor.move_left(&MoveLeft, window, cx);
 1622        editor.select_to_beginning_of_line(
 1623            &SelectToBeginningOfLine {
 1624                stop_at_soft_wraps: true,
 1625                stop_at_indent: true,
 1626            },
 1627            window,
 1628            cx,
 1629        );
 1630        assert_eq!(
 1631            editor.selections.display_ranges(cx),
 1632            &[
 1633                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1634                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1635            ]
 1636        );
 1637    });
 1638
 1639    _ = editor.update(cx, |editor, window, cx| {
 1640        editor.select_to_beginning_of_line(
 1641            &SelectToBeginningOfLine {
 1642                stop_at_soft_wraps: true,
 1643                stop_at_indent: true,
 1644            },
 1645            window,
 1646            cx,
 1647        );
 1648        assert_eq!(
 1649            editor.selections.display_ranges(cx),
 1650            &[
 1651                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1652                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1653            ]
 1654        );
 1655    });
 1656
 1657    _ = editor.update(cx, |editor, window, cx| {
 1658        editor.select_to_beginning_of_line(
 1659            &SelectToBeginningOfLine {
 1660                stop_at_soft_wraps: true,
 1661                stop_at_indent: true,
 1662            },
 1663            window,
 1664            cx,
 1665        );
 1666        assert_eq!(
 1667            editor.selections.display_ranges(cx),
 1668            &[
 1669                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1670                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1671            ]
 1672        );
 1673    });
 1674
 1675    _ = editor.update(cx, |editor, window, cx| {
 1676        editor.select_to_end_of_line(
 1677            &SelectToEndOfLine {
 1678                stop_at_soft_wraps: true,
 1679            },
 1680            window,
 1681            cx,
 1682        );
 1683        assert_eq!(
 1684            editor.selections.display_ranges(cx),
 1685            &[
 1686                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1687                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1688            ]
 1689        );
 1690    });
 1691
 1692    _ = editor.update(cx, |editor, window, cx| {
 1693        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1694        assert_eq!(editor.display_text(cx), "ab\n  de");
 1695        assert_eq!(
 1696            editor.selections.display_ranges(cx),
 1697            &[
 1698                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1699                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1700            ]
 1701        );
 1702    });
 1703
 1704    _ = editor.update(cx, |editor, window, cx| {
 1705        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1706        assert_eq!(editor.display_text(cx), "\n");
 1707        assert_eq!(
 1708            editor.selections.display_ranges(cx),
 1709            &[
 1710                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1711                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1712            ]
 1713        );
 1714    });
 1715}
 1716
 1717#[gpui::test]
 1718fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1719    init_test(cx, |_| {});
 1720    let move_to_beg = MoveToBeginningOfLine {
 1721        stop_at_soft_wraps: false,
 1722        stop_at_indent: false,
 1723    };
 1724
 1725    let move_to_end = MoveToEndOfLine {
 1726        stop_at_soft_wraps: false,
 1727    };
 1728
 1729    let editor = cx.add_window(|window, cx| {
 1730        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1731        build_editor(buffer, window, cx)
 1732    });
 1733
 1734    _ = editor.update(cx, |editor, window, cx| {
 1735        editor.set_wrap_width(Some(140.0.into()), cx);
 1736
 1737        // We expect the following lines after wrapping
 1738        // ```
 1739        // thequickbrownfox
 1740        // jumpedoverthelazydo
 1741        // gs
 1742        // ```
 1743        // The final `gs` was soft-wrapped onto a new line.
 1744        assert_eq!(
 1745            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1746            editor.display_text(cx),
 1747        );
 1748
 1749        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1750        // Start the cursor at the `k` on the first line
 1751        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1752            s.select_display_ranges([
 1753                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1754            ]);
 1755        });
 1756
 1757        // Moving to the beginning of the line should put us at the beginning of the line.
 1758        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1759        assert_eq!(
 1760            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1761            editor.selections.display_ranges(cx)
 1762        );
 1763
 1764        // Moving to the end of the line should put us at the end of the line.
 1765        editor.move_to_end_of_line(&move_to_end, window, cx);
 1766        assert_eq!(
 1767            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1768            editor.selections.display_ranges(cx)
 1769        );
 1770
 1771        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1772        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1773        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1774            s.select_display_ranges([
 1775                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1776            ]);
 1777        });
 1778
 1779        // Moving to the beginning of the line should put us at the start of the second line of
 1780        // display text, i.e., the `j`.
 1781        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1782        assert_eq!(
 1783            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1784            editor.selections.display_ranges(cx)
 1785        );
 1786
 1787        // Moving to the beginning of the line again should be a no-op.
 1788        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1789        assert_eq!(
 1790            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1791            editor.selections.display_ranges(cx)
 1792        );
 1793
 1794        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1795        // next display line.
 1796        editor.move_to_end_of_line(&move_to_end, window, cx);
 1797        assert_eq!(
 1798            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1799            editor.selections.display_ranges(cx)
 1800        );
 1801
 1802        // Moving to the end of the line again should be a no-op.
 1803        editor.move_to_end_of_line(&move_to_end, window, cx);
 1804        assert_eq!(
 1805            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1806            editor.selections.display_ranges(cx)
 1807        );
 1808    });
 1809}
 1810
 1811#[gpui::test]
 1812fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1813    init_test(cx, |_| {});
 1814
 1815    let move_to_beg = MoveToBeginningOfLine {
 1816        stop_at_soft_wraps: true,
 1817        stop_at_indent: true,
 1818    };
 1819
 1820    let select_to_beg = SelectToBeginningOfLine {
 1821        stop_at_soft_wraps: true,
 1822        stop_at_indent: true,
 1823    };
 1824
 1825    let delete_to_beg = DeleteToBeginningOfLine {
 1826        stop_at_indent: true,
 1827    };
 1828
 1829    let move_to_end = MoveToEndOfLine {
 1830        stop_at_soft_wraps: false,
 1831    };
 1832
 1833    let editor = cx.add_window(|window, cx| {
 1834        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1835        build_editor(buffer, window, cx)
 1836    });
 1837
 1838    _ = editor.update(cx, |editor, window, cx| {
 1839        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1840            s.select_display_ranges([
 1841                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1842                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1843            ]);
 1844        });
 1845
 1846        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1847        // and the second cursor at the first non-whitespace character in the line.
 1848        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1849        assert_eq!(
 1850            editor.selections.display_ranges(cx),
 1851            &[
 1852                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1853                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1854            ]
 1855        );
 1856
 1857        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1858        // and should move the second cursor to the beginning of the line.
 1859        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1860        assert_eq!(
 1861            editor.selections.display_ranges(cx),
 1862            &[
 1863                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1864                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1865            ]
 1866        );
 1867
 1868        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1869        // and should move the second cursor back to the first non-whitespace character in the line.
 1870        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1871        assert_eq!(
 1872            editor.selections.display_ranges(cx),
 1873            &[
 1874                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1875                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1876            ]
 1877        );
 1878
 1879        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1880        // and to the first non-whitespace character in the line for the second cursor.
 1881        editor.move_to_end_of_line(&move_to_end, window, cx);
 1882        editor.move_left(&MoveLeft, window, cx);
 1883        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1884        assert_eq!(
 1885            editor.selections.display_ranges(cx),
 1886            &[
 1887                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1888                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1889            ]
 1890        );
 1891
 1892        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1893        // and should select to the beginning of the line for the second cursor.
 1894        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1895        assert_eq!(
 1896            editor.selections.display_ranges(cx),
 1897            &[
 1898                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1899                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1900            ]
 1901        );
 1902
 1903        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1904        // and should delete to the first non-whitespace character in the line for the second cursor.
 1905        editor.move_to_end_of_line(&move_to_end, window, cx);
 1906        editor.move_left(&MoveLeft, window, cx);
 1907        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1908        assert_eq!(editor.text(cx), "c\n  f");
 1909    });
 1910}
 1911
 1912#[gpui::test]
 1913fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 1914    init_test(cx, |_| {});
 1915
 1916    let move_to_beg = MoveToBeginningOfLine {
 1917        stop_at_soft_wraps: true,
 1918        stop_at_indent: true,
 1919    };
 1920
 1921    let editor = cx.add_window(|window, cx| {
 1922        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 1923        build_editor(buffer, window, cx)
 1924    });
 1925
 1926    _ = editor.update(cx, |editor, window, cx| {
 1927        // test cursor between line_start and indent_start
 1928        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1929            s.select_display_ranges([
 1930                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 1931            ]);
 1932        });
 1933
 1934        // cursor should move to line_start
 1935        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1936        assert_eq!(
 1937            editor.selections.display_ranges(cx),
 1938            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1939        );
 1940
 1941        // cursor should move to indent_start
 1942        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1943        assert_eq!(
 1944            editor.selections.display_ranges(cx),
 1945            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 1946        );
 1947
 1948        // cursor should move to back to line_start
 1949        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1950        assert_eq!(
 1951            editor.selections.display_ranges(cx),
 1952            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1953        );
 1954    });
 1955}
 1956
 1957#[gpui::test]
 1958fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1959    init_test(cx, |_| {});
 1960
 1961    let editor = cx.add_window(|window, cx| {
 1962        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1963        build_editor(buffer, window, cx)
 1964    });
 1965    _ = editor.update(cx, |editor, window, cx| {
 1966        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1967            s.select_display_ranges([
 1968                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1969                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1970            ])
 1971        });
 1972        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1973        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1974
 1975        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1976        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1977
 1978        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1979        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1980
 1981        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1982        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1983
 1984        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1985        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 1986
 1987        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1988        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1989
 1990        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1991        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1992
 1993        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1994        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1995
 1996        editor.move_right(&MoveRight, window, cx);
 1997        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1998        assert_selection_ranges(
 1999            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 2000            editor,
 2001            cx,
 2002        );
 2003
 2004        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2005        assert_selection_ranges(
 2006            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2007            editor,
 2008            cx,
 2009        );
 2010
 2011        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2012        assert_selection_ranges(
 2013            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2014            editor,
 2015            cx,
 2016        );
 2017    });
 2018}
 2019
 2020#[gpui::test]
 2021fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2022    init_test(cx, |_| {});
 2023
 2024    let editor = cx.add_window(|window, cx| {
 2025        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2026        build_editor(buffer, window, cx)
 2027    });
 2028
 2029    _ = editor.update(cx, |editor, window, cx| {
 2030        editor.set_wrap_width(Some(140.0.into()), cx);
 2031        assert_eq!(
 2032            editor.display_text(cx),
 2033            "use one::{\n    two::three::\n    four::five\n};"
 2034        );
 2035
 2036        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2037            s.select_display_ranges([
 2038                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2039            ]);
 2040        });
 2041
 2042        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2043        assert_eq!(
 2044            editor.selections.display_ranges(cx),
 2045            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2046        );
 2047
 2048        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2049        assert_eq!(
 2050            editor.selections.display_ranges(cx),
 2051            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2052        );
 2053
 2054        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2055        assert_eq!(
 2056            editor.selections.display_ranges(cx),
 2057            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2058        );
 2059
 2060        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2061        assert_eq!(
 2062            editor.selections.display_ranges(cx),
 2063            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2064        );
 2065
 2066        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2067        assert_eq!(
 2068            editor.selections.display_ranges(cx),
 2069            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2070        );
 2071
 2072        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2073        assert_eq!(
 2074            editor.selections.display_ranges(cx),
 2075            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2076        );
 2077    });
 2078}
 2079
 2080#[gpui::test]
 2081async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2082    init_test(cx, |_| {});
 2083    let mut cx = EditorTestContext::new(cx).await;
 2084
 2085    let line_height = cx.editor(|editor, window, _| {
 2086        editor
 2087            .style()
 2088            .unwrap()
 2089            .text
 2090            .line_height_in_pixels(window.rem_size())
 2091    });
 2092    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2093
 2094    cx.set_state(
 2095        &r#"ˇone
 2096        two
 2097
 2098        three
 2099        fourˇ
 2100        five
 2101
 2102        six"#
 2103            .unindent(),
 2104    );
 2105
 2106    cx.update_editor(|editor, window, cx| {
 2107        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2108    });
 2109    cx.assert_editor_state(
 2110        &r#"one
 2111        two
 2112        ˇ
 2113        three
 2114        four
 2115        five
 2116        ˇ
 2117        six"#
 2118            .unindent(),
 2119    );
 2120
 2121    cx.update_editor(|editor, window, cx| {
 2122        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2123    });
 2124    cx.assert_editor_state(
 2125        &r#"one
 2126        two
 2127
 2128        three
 2129        four
 2130        five
 2131        ˇ
 2132        sixˇ"#
 2133            .unindent(),
 2134    );
 2135
 2136    cx.update_editor(|editor, window, cx| {
 2137        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2138    });
 2139    cx.assert_editor_state(
 2140        &r#"one
 2141        two
 2142
 2143        three
 2144        four
 2145        five
 2146
 2147        sixˇ"#
 2148            .unindent(),
 2149    );
 2150
 2151    cx.update_editor(|editor, window, cx| {
 2152        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2153    });
 2154    cx.assert_editor_state(
 2155        &r#"one
 2156        two
 2157
 2158        three
 2159        four
 2160        five
 2161        ˇ
 2162        six"#
 2163            .unindent(),
 2164    );
 2165
 2166    cx.update_editor(|editor, window, cx| {
 2167        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2168    });
 2169    cx.assert_editor_state(
 2170        &r#"one
 2171        two
 2172        ˇ
 2173        three
 2174        four
 2175        five
 2176
 2177        six"#
 2178            .unindent(),
 2179    );
 2180
 2181    cx.update_editor(|editor, window, cx| {
 2182        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2183    });
 2184    cx.assert_editor_state(
 2185        &r#"ˇone
 2186        two
 2187
 2188        three
 2189        four
 2190        five
 2191
 2192        six"#
 2193            .unindent(),
 2194    );
 2195}
 2196
 2197#[gpui::test]
 2198async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2199    init_test(cx, |_| {});
 2200    let mut cx = EditorTestContext::new(cx).await;
 2201    let line_height = cx.editor(|editor, window, _| {
 2202        editor
 2203            .style()
 2204            .unwrap()
 2205            .text
 2206            .line_height_in_pixels(window.rem_size())
 2207    });
 2208    let window = cx.window;
 2209    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2210
 2211    cx.set_state(
 2212        r#"ˇone
 2213        two
 2214        three
 2215        four
 2216        five
 2217        six
 2218        seven
 2219        eight
 2220        nine
 2221        ten
 2222        "#,
 2223    );
 2224
 2225    cx.update_editor(|editor, window, cx| {
 2226        assert_eq!(
 2227            editor.snapshot(window, cx).scroll_position(),
 2228            gpui::Point::new(0., 0.)
 2229        );
 2230        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2231        assert_eq!(
 2232            editor.snapshot(window, cx).scroll_position(),
 2233            gpui::Point::new(0., 3.)
 2234        );
 2235        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2236        assert_eq!(
 2237            editor.snapshot(window, cx).scroll_position(),
 2238            gpui::Point::new(0., 6.)
 2239        );
 2240        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2241        assert_eq!(
 2242            editor.snapshot(window, cx).scroll_position(),
 2243            gpui::Point::new(0., 3.)
 2244        );
 2245
 2246        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2247        assert_eq!(
 2248            editor.snapshot(window, cx).scroll_position(),
 2249            gpui::Point::new(0., 1.)
 2250        );
 2251        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2252        assert_eq!(
 2253            editor.snapshot(window, cx).scroll_position(),
 2254            gpui::Point::new(0., 3.)
 2255        );
 2256    });
 2257}
 2258
 2259#[gpui::test]
 2260async fn test_autoscroll(cx: &mut TestAppContext) {
 2261    init_test(cx, |_| {});
 2262    let mut cx = EditorTestContext::new(cx).await;
 2263
 2264    let line_height = cx.update_editor(|editor, window, cx| {
 2265        editor.set_vertical_scroll_margin(2, cx);
 2266        editor
 2267            .style()
 2268            .unwrap()
 2269            .text
 2270            .line_height_in_pixels(window.rem_size())
 2271    });
 2272    let window = cx.window;
 2273    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2274
 2275    cx.set_state(
 2276        r#"ˇone
 2277            two
 2278            three
 2279            four
 2280            five
 2281            six
 2282            seven
 2283            eight
 2284            nine
 2285            ten
 2286        "#,
 2287    );
 2288    cx.update_editor(|editor, window, cx| {
 2289        assert_eq!(
 2290            editor.snapshot(window, cx).scroll_position(),
 2291            gpui::Point::new(0., 0.0)
 2292        );
 2293    });
 2294
 2295    // Add a cursor below the visible area. Since both cursors cannot fit
 2296    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2297    // allows the vertical scroll margin below that cursor.
 2298    cx.update_editor(|editor, window, cx| {
 2299        editor.change_selections(Default::default(), window, cx, |selections| {
 2300            selections.select_ranges([
 2301                Point::new(0, 0)..Point::new(0, 0),
 2302                Point::new(6, 0)..Point::new(6, 0),
 2303            ]);
 2304        })
 2305    });
 2306    cx.update_editor(|editor, window, cx| {
 2307        assert_eq!(
 2308            editor.snapshot(window, cx).scroll_position(),
 2309            gpui::Point::new(0., 3.0)
 2310        );
 2311    });
 2312
 2313    // Move down. The editor cursor scrolls down to track the newest cursor.
 2314    cx.update_editor(|editor, window, cx| {
 2315        editor.move_down(&Default::default(), window, cx);
 2316    });
 2317    cx.update_editor(|editor, window, cx| {
 2318        assert_eq!(
 2319            editor.snapshot(window, cx).scroll_position(),
 2320            gpui::Point::new(0., 4.0)
 2321        );
 2322    });
 2323
 2324    // Add a cursor above the visible area. Since both cursors fit on screen,
 2325    // the editor scrolls to show both.
 2326    cx.update_editor(|editor, window, cx| {
 2327        editor.change_selections(Default::default(), window, cx, |selections| {
 2328            selections.select_ranges([
 2329                Point::new(1, 0)..Point::new(1, 0),
 2330                Point::new(6, 0)..Point::new(6, 0),
 2331            ]);
 2332        })
 2333    });
 2334    cx.update_editor(|editor, window, cx| {
 2335        assert_eq!(
 2336            editor.snapshot(window, cx).scroll_position(),
 2337            gpui::Point::new(0., 1.0)
 2338        );
 2339    });
 2340}
 2341
 2342#[gpui::test]
 2343async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2344    init_test(cx, |_| {});
 2345    let mut cx = EditorTestContext::new(cx).await;
 2346
 2347    let line_height = cx.editor(|editor, window, _cx| {
 2348        editor
 2349            .style()
 2350            .unwrap()
 2351            .text
 2352            .line_height_in_pixels(window.rem_size())
 2353    });
 2354    let window = cx.window;
 2355    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2356    cx.set_state(
 2357        &r#"
 2358        ˇone
 2359        two
 2360        threeˇ
 2361        four
 2362        five
 2363        six
 2364        seven
 2365        eight
 2366        nine
 2367        ten
 2368        "#
 2369        .unindent(),
 2370    );
 2371
 2372    cx.update_editor(|editor, window, cx| {
 2373        editor.move_page_down(&MovePageDown::default(), window, cx)
 2374    });
 2375    cx.assert_editor_state(
 2376        &r#"
 2377        one
 2378        two
 2379        three
 2380        ˇfour
 2381        five
 2382        sixˇ
 2383        seven
 2384        eight
 2385        nine
 2386        ten
 2387        "#
 2388        .unindent(),
 2389    );
 2390
 2391    cx.update_editor(|editor, window, cx| {
 2392        editor.move_page_down(&MovePageDown::default(), window, cx)
 2393    });
 2394    cx.assert_editor_state(
 2395        &r#"
 2396        one
 2397        two
 2398        three
 2399        four
 2400        five
 2401        six
 2402        ˇseven
 2403        eight
 2404        nineˇ
 2405        ten
 2406        "#
 2407        .unindent(),
 2408    );
 2409
 2410    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2411    cx.assert_editor_state(
 2412        &r#"
 2413        one
 2414        two
 2415        three
 2416        ˇfour
 2417        five
 2418        sixˇ
 2419        seven
 2420        eight
 2421        nine
 2422        ten
 2423        "#
 2424        .unindent(),
 2425    );
 2426
 2427    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2428    cx.assert_editor_state(
 2429        &r#"
 2430        ˇone
 2431        two
 2432        threeˇ
 2433        four
 2434        five
 2435        six
 2436        seven
 2437        eight
 2438        nine
 2439        ten
 2440        "#
 2441        .unindent(),
 2442    );
 2443
 2444    // Test select collapsing
 2445    cx.update_editor(|editor, window, cx| {
 2446        editor.move_page_down(&MovePageDown::default(), window, cx);
 2447        editor.move_page_down(&MovePageDown::default(), window, cx);
 2448        editor.move_page_down(&MovePageDown::default(), window, cx);
 2449    });
 2450    cx.assert_editor_state(
 2451        &r#"
 2452        one
 2453        two
 2454        three
 2455        four
 2456        five
 2457        six
 2458        seven
 2459        eight
 2460        nine
 2461        ˇten
 2462        ˇ"#
 2463        .unindent(),
 2464    );
 2465}
 2466
 2467#[gpui::test]
 2468async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2469    init_test(cx, |_| {});
 2470    let mut cx = EditorTestContext::new(cx).await;
 2471    cx.set_state("one «two threeˇ» four");
 2472    cx.update_editor(|editor, window, cx| {
 2473        editor.delete_to_beginning_of_line(
 2474            &DeleteToBeginningOfLine {
 2475                stop_at_indent: false,
 2476            },
 2477            window,
 2478            cx,
 2479        );
 2480        assert_eq!(editor.text(cx), " four");
 2481    });
 2482}
 2483
 2484#[gpui::test]
 2485async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2486    init_test(cx, |_| {});
 2487
 2488    let mut cx = EditorTestContext::new(cx).await;
 2489
 2490    // For an empty selection, the preceding word fragment is deleted.
 2491    // For non-empty selections, only selected characters are deleted.
 2492    cx.set_state("onˇe two t«hreˇ»e four");
 2493    cx.update_editor(|editor, window, cx| {
 2494        editor.delete_to_previous_word_start(
 2495            &DeleteToPreviousWordStart {
 2496                ignore_newlines: false,
 2497                ignore_brackets: false,
 2498            },
 2499            window,
 2500            cx,
 2501        );
 2502    });
 2503    cx.assert_editor_state("ˇe two tˇe four");
 2504
 2505    cx.set_state("e tˇwo te «fˇ»our");
 2506    cx.update_editor(|editor, window, cx| {
 2507        editor.delete_to_next_word_end(
 2508            &DeleteToNextWordEnd {
 2509                ignore_newlines: false,
 2510                ignore_brackets: false,
 2511            },
 2512            window,
 2513            cx,
 2514        );
 2515    });
 2516    cx.assert_editor_state("e tˇ te ˇour");
 2517}
 2518
 2519#[gpui::test]
 2520async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2521    init_test(cx, |_| {});
 2522
 2523    let mut cx = EditorTestContext::new(cx).await;
 2524
 2525    cx.set_state("here is some text    ˇwith a space");
 2526    cx.update_editor(|editor, window, cx| {
 2527        editor.delete_to_previous_word_start(
 2528            &DeleteToPreviousWordStart {
 2529                ignore_newlines: false,
 2530                ignore_brackets: true,
 2531            },
 2532            window,
 2533            cx,
 2534        );
 2535    });
 2536    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 2537    cx.assert_editor_state("here is some textˇwith a space");
 2538
 2539    cx.set_state("here is some text    ˇwith a space");
 2540    cx.update_editor(|editor, window, cx| {
 2541        editor.delete_to_previous_word_start(
 2542            &DeleteToPreviousWordStart {
 2543                ignore_newlines: false,
 2544                ignore_brackets: false,
 2545            },
 2546            window,
 2547            cx,
 2548        );
 2549    });
 2550    cx.assert_editor_state("here is some textˇwith a space");
 2551
 2552    cx.set_state("here is some textˇ    with a space");
 2553    cx.update_editor(|editor, window, cx| {
 2554        editor.delete_to_next_word_end(
 2555            &DeleteToNextWordEnd {
 2556                ignore_newlines: false,
 2557                ignore_brackets: true,
 2558            },
 2559            window,
 2560            cx,
 2561        );
 2562    });
 2563    // Same happens in the other direction.
 2564    cx.assert_editor_state("here is some textˇwith a space");
 2565
 2566    cx.set_state("here is some textˇ    with a space");
 2567    cx.update_editor(|editor, window, cx| {
 2568        editor.delete_to_next_word_end(
 2569            &DeleteToNextWordEnd {
 2570                ignore_newlines: false,
 2571                ignore_brackets: false,
 2572            },
 2573            window,
 2574            cx,
 2575        );
 2576    });
 2577    cx.assert_editor_state("here is some textˇwith a space");
 2578
 2579    cx.set_state("here is some textˇ    with a space");
 2580    cx.update_editor(|editor, window, cx| {
 2581        editor.delete_to_next_word_end(
 2582            &DeleteToNextWordEnd {
 2583                ignore_newlines: true,
 2584                ignore_brackets: false,
 2585            },
 2586            window,
 2587            cx,
 2588        );
 2589    });
 2590    cx.assert_editor_state("here is some textˇwith a space");
 2591    cx.update_editor(|editor, window, cx| {
 2592        editor.delete_to_previous_word_start(
 2593            &DeleteToPreviousWordStart {
 2594                ignore_newlines: true,
 2595                ignore_brackets: false,
 2596            },
 2597            window,
 2598            cx,
 2599        );
 2600    });
 2601    cx.assert_editor_state("here is some ˇwith a space");
 2602    cx.update_editor(|editor, window, cx| {
 2603        editor.delete_to_previous_word_start(
 2604            &DeleteToPreviousWordStart {
 2605                ignore_newlines: true,
 2606                ignore_brackets: false,
 2607            },
 2608            window,
 2609            cx,
 2610        );
 2611    });
 2612    // Single whitespaces are removed with the word behind them.
 2613    cx.assert_editor_state("here is ˇwith a space");
 2614    cx.update_editor(|editor, window, cx| {
 2615        editor.delete_to_previous_word_start(
 2616            &DeleteToPreviousWordStart {
 2617                ignore_newlines: true,
 2618                ignore_brackets: false,
 2619            },
 2620            window,
 2621            cx,
 2622        );
 2623    });
 2624    cx.assert_editor_state("here ˇwith a space");
 2625    cx.update_editor(|editor, window, cx| {
 2626        editor.delete_to_previous_word_start(
 2627            &DeleteToPreviousWordStart {
 2628                ignore_newlines: true,
 2629                ignore_brackets: false,
 2630            },
 2631            window,
 2632            cx,
 2633        );
 2634    });
 2635    cx.assert_editor_state("ˇwith a space");
 2636    cx.update_editor(|editor, window, cx| {
 2637        editor.delete_to_previous_word_start(
 2638            &DeleteToPreviousWordStart {
 2639                ignore_newlines: true,
 2640                ignore_brackets: false,
 2641            },
 2642            window,
 2643            cx,
 2644        );
 2645    });
 2646    cx.assert_editor_state("ˇwith a space");
 2647    cx.update_editor(|editor, window, cx| {
 2648        editor.delete_to_next_word_end(
 2649            &DeleteToNextWordEnd {
 2650                ignore_newlines: true,
 2651                ignore_brackets: false,
 2652            },
 2653            window,
 2654            cx,
 2655        );
 2656    });
 2657    // Same happens in the other direction.
 2658    cx.assert_editor_state("ˇ a space");
 2659    cx.update_editor(|editor, window, cx| {
 2660        editor.delete_to_next_word_end(
 2661            &DeleteToNextWordEnd {
 2662                ignore_newlines: true,
 2663                ignore_brackets: false,
 2664            },
 2665            window,
 2666            cx,
 2667        );
 2668    });
 2669    cx.assert_editor_state("ˇ space");
 2670    cx.update_editor(|editor, window, cx| {
 2671        editor.delete_to_next_word_end(
 2672            &DeleteToNextWordEnd {
 2673                ignore_newlines: true,
 2674                ignore_brackets: false,
 2675            },
 2676            window,
 2677            cx,
 2678        );
 2679    });
 2680    cx.assert_editor_state("ˇ");
 2681    cx.update_editor(|editor, window, cx| {
 2682        editor.delete_to_next_word_end(
 2683            &DeleteToNextWordEnd {
 2684                ignore_newlines: true,
 2685                ignore_brackets: false,
 2686            },
 2687            window,
 2688            cx,
 2689        );
 2690    });
 2691    cx.assert_editor_state("ˇ");
 2692    cx.update_editor(|editor, window, cx| {
 2693        editor.delete_to_previous_word_start(
 2694            &DeleteToPreviousWordStart {
 2695                ignore_newlines: true,
 2696                ignore_brackets: false,
 2697            },
 2698            window,
 2699            cx,
 2700        );
 2701    });
 2702    cx.assert_editor_state("ˇ");
 2703}
 2704
 2705#[gpui::test]
 2706async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2707    init_test(cx, |_| {});
 2708
 2709    let language = Arc::new(
 2710        Language::new(
 2711            LanguageConfig {
 2712                brackets: BracketPairConfig {
 2713                    pairs: vec![
 2714                        BracketPair {
 2715                            start: "\"".to_string(),
 2716                            end: "\"".to_string(),
 2717                            close: true,
 2718                            surround: true,
 2719                            newline: false,
 2720                        },
 2721                        BracketPair {
 2722                            start: "(".to_string(),
 2723                            end: ")".to_string(),
 2724                            close: true,
 2725                            surround: true,
 2726                            newline: true,
 2727                        },
 2728                    ],
 2729                    ..BracketPairConfig::default()
 2730                },
 2731                ..LanguageConfig::default()
 2732            },
 2733            Some(tree_sitter_rust::LANGUAGE.into()),
 2734        )
 2735        .with_brackets_query(
 2736            r#"
 2737                ("(" @open ")" @close)
 2738                ("\"" @open "\"" @close)
 2739            "#,
 2740        )
 2741        .unwrap(),
 2742    );
 2743
 2744    let mut cx = EditorTestContext::new(cx).await;
 2745    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2746
 2747    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2748    cx.update_editor(|editor, window, cx| {
 2749        editor.delete_to_previous_word_start(
 2750            &DeleteToPreviousWordStart {
 2751                ignore_newlines: true,
 2752                ignore_brackets: false,
 2753            },
 2754            window,
 2755            cx,
 2756        );
 2757    });
 2758    // Deletion stops before brackets if asked to not ignore them.
 2759    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 2760    cx.update_editor(|editor, window, cx| {
 2761        editor.delete_to_previous_word_start(
 2762            &DeleteToPreviousWordStart {
 2763                ignore_newlines: true,
 2764                ignore_brackets: false,
 2765            },
 2766            window,
 2767            cx,
 2768        );
 2769    });
 2770    // Deletion has to remove a single bracket and then stop again.
 2771    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 2772
 2773    cx.update_editor(|editor, window, cx| {
 2774        editor.delete_to_previous_word_start(
 2775            &DeleteToPreviousWordStart {
 2776                ignore_newlines: true,
 2777                ignore_brackets: false,
 2778            },
 2779            window,
 2780            cx,
 2781        );
 2782    });
 2783    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 2784
 2785    cx.update_editor(|editor, window, cx| {
 2786        editor.delete_to_previous_word_start(
 2787            &DeleteToPreviousWordStart {
 2788                ignore_newlines: true,
 2789                ignore_brackets: false,
 2790            },
 2791            window,
 2792            cx,
 2793        );
 2794    });
 2795    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2796
 2797    cx.update_editor(|editor, window, cx| {
 2798        editor.delete_to_previous_word_start(
 2799            &DeleteToPreviousWordStart {
 2800                ignore_newlines: true,
 2801                ignore_brackets: false,
 2802            },
 2803            window,
 2804            cx,
 2805        );
 2806    });
 2807    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2808
 2809    cx.update_editor(|editor, window, cx| {
 2810        editor.delete_to_next_word_end(
 2811            &DeleteToNextWordEnd {
 2812                ignore_newlines: true,
 2813                ignore_brackets: false,
 2814            },
 2815            window,
 2816            cx,
 2817        );
 2818    });
 2819    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 2820    cx.assert_editor_state(r#"ˇ");"#);
 2821
 2822    cx.update_editor(|editor, window, cx| {
 2823        editor.delete_to_next_word_end(
 2824            &DeleteToNextWordEnd {
 2825                ignore_newlines: true,
 2826                ignore_brackets: false,
 2827            },
 2828            window,
 2829            cx,
 2830        );
 2831    });
 2832    cx.assert_editor_state(r#"ˇ"#);
 2833
 2834    cx.update_editor(|editor, window, cx| {
 2835        editor.delete_to_next_word_end(
 2836            &DeleteToNextWordEnd {
 2837                ignore_newlines: true,
 2838                ignore_brackets: false,
 2839            },
 2840            window,
 2841            cx,
 2842        );
 2843    });
 2844    cx.assert_editor_state(r#"ˇ"#);
 2845
 2846    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2847    cx.update_editor(|editor, window, cx| {
 2848        editor.delete_to_previous_word_start(
 2849            &DeleteToPreviousWordStart {
 2850                ignore_newlines: true,
 2851                ignore_brackets: true,
 2852            },
 2853            window,
 2854            cx,
 2855        );
 2856    });
 2857    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 2858}
 2859
 2860#[gpui::test]
 2861fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2862    init_test(cx, |_| {});
 2863
 2864    let editor = cx.add_window(|window, cx| {
 2865        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2866        build_editor(buffer, window, cx)
 2867    });
 2868    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2869        ignore_newlines: false,
 2870        ignore_brackets: false,
 2871    };
 2872    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2873        ignore_newlines: true,
 2874        ignore_brackets: false,
 2875    };
 2876
 2877    _ = editor.update(cx, |editor, window, cx| {
 2878        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2879            s.select_display_ranges([
 2880                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2881            ])
 2882        });
 2883        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2884        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2885        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2886        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2887        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2888        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2889        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2890        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2891        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2892        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2893        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2894        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2895    });
 2896}
 2897
 2898#[gpui::test]
 2899fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2900    init_test(cx, |_| {});
 2901
 2902    let editor = cx.add_window(|window, cx| {
 2903        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2904        build_editor(buffer, window, cx)
 2905    });
 2906    let del_to_next_word_end = DeleteToNextWordEnd {
 2907        ignore_newlines: false,
 2908        ignore_brackets: false,
 2909    };
 2910    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2911        ignore_newlines: true,
 2912        ignore_brackets: false,
 2913    };
 2914
 2915    _ = editor.update(cx, |editor, window, cx| {
 2916        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2917            s.select_display_ranges([
 2918                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2919            ])
 2920        });
 2921        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2922        assert_eq!(
 2923            editor.buffer.read(cx).read(cx).text(),
 2924            "one\n   two\nthree\n   four"
 2925        );
 2926        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2927        assert_eq!(
 2928            editor.buffer.read(cx).read(cx).text(),
 2929            "\n   two\nthree\n   four"
 2930        );
 2931        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2932        assert_eq!(
 2933            editor.buffer.read(cx).read(cx).text(),
 2934            "two\nthree\n   four"
 2935        );
 2936        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2937        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2938        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2939        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2940        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2941        assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
 2942        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2943        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2944    });
 2945}
 2946
 2947#[gpui::test]
 2948fn test_newline(cx: &mut TestAppContext) {
 2949    init_test(cx, |_| {});
 2950
 2951    let editor = cx.add_window(|window, cx| {
 2952        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2953        build_editor(buffer, window, cx)
 2954    });
 2955
 2956    _ = editor.update(cx, |editor, window, cx| {
 2957        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2958            s.select_display_ranges([
 2959                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2960                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2961                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2962            ])
 2963        });
 2964
 2965        editor.newline(&Newline, window, cx);
 2966        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2967    });
 2968}
 2969
 2970#[gpui::test]
 2971fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2972    init_test(cx, |_| {});
 2973
 2974    let editor = cx.add_window(|window, cx| {
 2975        let buffer = MultiBuffer::build_simple(
 2976            "
 2977                a
 2978                b(
 2979                    X
 2980                )
 2981                c(
 2982                    X
 2983                )
 2984            "
 2985            .unindent()
 2986            .as_str(),
 2987            cx,
 2988        );
 2989        let mut editor = build_editor(buffer, window, cx);
 2990        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2991            s.select_ranges([
 2992                Point::new(2, 4)..Point::new(2, 5),
 2993                Point::new(5, 4)..Point::new(5, 5),
 2994            ])
 2995        });
 2996        editor
 2997    });
 2998
 2999    _ = editor.update(cx, |editor, window, cx| {
 3000        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3001        editor.buffer.update(cx, |buffer, cx| {
 3002            buffer.edit(
 3003                [
 3004                    (Point::new(1, 2)..Point::new(3, 0), ""),
 3005                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3006                ],
 3007                None,
 3008                cx,
 3009            );
 3010            assert_eq!(
 3011                buffer.read(cx).text(),
 3012                "
 3013                    a
 3014                    b()
 3015                    c()
 3016                "
 3017                .unindent()
 3018            );
 3019        });
 3020        assert_eq!(
 3021            editor.selections.ranges(cx),
 3022            &[
 3023                Point::new(1, 2)..Point::new(1, 2),
 3024                Point::new(2, 2)..Point::new(2, 2),
 3025            ],
 3026        );
 3027
 3028        editor.newline(&Newline, window, cx);
 3029        assert_eq!(
 3030            editor.text(cx),
 3031            "
 3032                a
 3033                b(
 3034                )
 3035                c(
 3036                )
 3037            "
 3038            .unindent()
 3039        );
 3040
 3041        // The selections are moved after the inserted newlines
 3042        assert_eq!(
 3043            editor.selections.ranges(cx),
 3044            &[
 3045                Point::new(2, 0)..Point::new(2, 0),
 3046                Point::new(4, 0)..Point::new(4, 0),
 3047            ],
 3048        );
 3049    });
 3050}
 3051
 3052#[gpui::test]
 3053async fn test_newline_above(cx: &mut TestAppContext) {
 3054    init_test(cx, |settings| {
 3055        settings.defaults.tab_size = NonZeroU32::new(4)
 3056    });
 3057
 3058    let language = Arc::new(
 3059        Language::new(
 3060            LanguageConfig::default(),
 3061            Some(tree_sitter_rust::LANGUAGE.into()),
 3062        )
 3063        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3064        .unwrap(),
 3065    );
 3066
 3067    let mut cx = EditorTestContext::new(cx).await;
 3068    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3069    cx.set_state(indoc! {"
 3070        const a: ˇA = (
 3071 3072                «const_functionˇ»(ˇ),
 3073                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3074 3075        ˇ);ˇ
 3076    "});
 3077
 3078    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3079    cx.assert_editor_state(indoc! {"
 3080        ˇ
 3081        const a: A = (
 3082            ˇ
 3083            (
 3084                ˇ
 3085                ˇ
 3086                const_function(),
 3087                ˇ
 3088                ˇ
 3089                ˇ
 3090                ˇ
 3091                something_else,
 3092                ˇ
 3093            )
 3094            ˇ
 3095            ˇ
 3096        );
 3097    "});
 3098}
 3099
 3100#[gpui::test]
 3101async fn test_newline_below(cx: &mut TestAppContext) {
 3102    init_test(cx, |settings| {
 3103        settings.defaults.tab_size = NonZeroU32::new(4)
 3104    });
 3105
 3106    let language = Arc::new(
 3107        Language::new(
 3108            LanguageConfig::default(),
 3109            Some(tree_sitter_rust::LANGUAGE.into()),
 3110        )
 3111        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3112        .unwrap(),
 3113    );
 3114
 3115    let mut cx = EditorTestContext::new(cx).await;
 3116    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3117    cx.set_state(indoc! {"
 3118        const a: ˇA = (
 3119 3120                «const_functionˇ»(ˇ),
 3121                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3122 3123        ˇ);ˇ
 3124    "});
 3125
 3126    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3127    cx.assert_editor_state(indoc! {"
 3128        const a: A = (
 3129            ˇ
 3130            (
 3131                ˇ
 3132                const_function(),
 3133                ˇ
 3134                ˇ
 3135                something_else,
 3136                ˇ
 3137                ˇ
 3138                ˇ
 3139                ˇ
 3140            )
 3141            ˇ
 3142        );
 3143        ˇ
 3144        ˇ
 3145    "});
 3146}
 3147
 3148#[gpui::test]
 3149async fn test_newline_comments(cx: &mut TestAppContext) {
 3150    init_test(cx, |settings| {
 3151        settings.defaults.tab_size = NonZeroU32::new(4)
 3152    });
 3153
 3154    let language = Arc::new(Language::new(
 3155        LanguageConfig {
 3156            line_comments: vec!["// ".into()],
 3157            ..LanguageConfig::default()
 3158        },
 3159        None,
 3160    ));
 3161    {
 3162        let mut cx = EditorTestContext::new(cx).await;
 3163        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3164        cx.set_state(indoc! {"
 3165        // Fooˇ
 3166    "});
 3167
 3168        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3169        cx.assert_editor_state(indoc! {"
 3170        // Foo
 3171        // ˇ
 3172    "});
 3173        // Ensure that we add comment prefix when existing line contains space
 3174        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3175        cx.assert_editor_state(
 3176            indoc! {"
 3177        // Foo
 3178        //s
 3179        // ˇ
 3180    "}
 3181            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3182            .as_str(),
 3183        );
 3184        // Ensure that we add comment prefix when existing line does not contain space
 3185        cx.set_state(indoc! {"
 3186        // Foo
 3187        //ˇ
 3188    "});
 3189        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3190        cx.assert_editor_state(indoc! {"
 3191        // Foo
 3192        //
 3193        // ˇ
 3194    "});
 3195        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3196        cx.set_state(indoc! {"
 3197        ˇ// Foo
 3198    "});
 3199        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3200        cx.assert_editor_state(indoc! {"
 3201
 3202        ˇ// Foo
 3203    "});
 3204    }
 3205    // Ensure that comment continuations can be disabled.
 3206    update_test_language_settings(cx, |settings| {
 3207        settings.defaults.extend_comment_on_newline = Some(false);
 3208    });
 3209    let mut cx = EditorTestContext::new(cx).await;
 3210    cx.set_state(indoc! {"
 3211        // Fooˇ
 3212    "});
 3213    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3214    cx.assert_editor_state(indoc! {"
 3215        // Foo
 3216        ˇ
 3217    "});
 3218}
 3219
 3220#[gpui::test]
 3221async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3222    init_test(cx, |settings| {
 3223        settings.defaults.tab_size = NonZeroU32::new(4)
 3224    });
 3225
 3226    let language = Arc::new(Language::new(
 3227        LanguageConfig {
 3228            line_comments: vec!["// ".into(), "/// ".into()],
 3229            ..LanguageConfig::default()
 3230        },
 3231        None,
 3232    ));
 3233    {
 3234        let mut cx = EditorTestContext::new(cx).await;
 3235        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3236        cx.set_state(indoc! {"
 3237        //ˇ
 3238    "});
 3239        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3240        cx.assert_editor_state(indoc! {"
 3241        //
 3242        // ˇ
 3243    "});
 3244
 3245        cx.set_state(indoc! {"
 3246        ///ˇ
 3247    "});
 3248        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3249        cx.assert_editor_state(indoc! {"
 3250        ///
 3251        /// ˇ
 3252    "});
 3253    }
 3254}
 3255
 3256#[gpui::test]
 3257async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3258    init_test(cx, |settings| {
 3259        settings.defaults.tab_size = NonZeroU32::new(4)
 3260    });
 3261
 3262    let language = Arc::new(
 3263        Language::new(
 3264            LanguageConfig {
 3265                documentation_comment: Some(language::BlockCommentConfig {
 3266                    start: "/**".into(),
 3267                    end: "*/".into(),
 3268                    prefix: "* ".into(),
 3269                    tab_size: 1,
 3270                }),
 3271
 3272                ..LanguageConfig::default()
 3273            },
 3274            Some(tree_sitter_rust::LANGUAGE.into()),
 3275        )
 3276        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3277        .unwrap(),
 3278    );
 3279
 3280    {
 3281        let mut cx = EditorTestContext::new(cx).await;
 3282        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3283        cx.set_state(indoc! {"
 3284        /**ˇ
 3285    "});
 3286
 3287        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3288        cx.assert_editor_state(indoc! {"
 3289        /**
 3290         * ˇ
 3291    "});
 3292        // Ensure that if cursor is before the comment start,
 3293        // we do not actually insert a comment prefix.
 3294        cx.set_state(indoc! {"
 3295        ˇ/**
 3296    "});
 3297        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3298        cx.assert_editor_state(indoc! {"
 3299
 3300        ˇ/**
 3301    "});
 3302        // Ensure that if cursor is between it doesn't add comment prefix.
 3303        cx.set_state(indoc! {"
 3304        /*ˇ*
 3305    "});
 3306        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3307        cx.assert_editor_state(indoc! {"
 3308        /*
 3309        ˇ*
 3310    "});
 3311        // Ensure that if suffix exists on same line after cursor it adds new line.
 3312        cx.set_state(indoc! {"
 3313        /**ˇ*/
 3314    "});
 3315        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3316        cx.assert_editor_state(indoc! {"
 3317        /**
 3318         * ˇ
 3319         */
 3320    "});
 3321        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3322        cx.set_state(indoc! {"
 3323        /**ˇ */
 3324    "});
 3325        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3326        cx.assert_editor_state(indoc! {"
 3327        /**
 3328         * ˇ
 3329         */
 3330    "});
 3331        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3332        cx.set_state(indoc! {"
 3333        /** ˇ*/
 3334    "});
 3335        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3336        cx.assert_editor_state(
 3337            indoc! {"
 3338        /**s
 3339         * ˇ
 3340         */
 3341    "}
 3342            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3343            .as_str(),
 3344        );
 3345        // Ensure that delimiter space is preserved when newline on already
 3346        // spaced delimiter.
 3347        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3348        cx.assert_editor_state(
 3349            indoc! {"
 3350        /**s
 3351         *s
 3352         * ˇ
 3353         */
 3354    "}
 3355            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3356            .as_str(),
 3357        );
 3358        // Ensure that delimiter space is preserved when space is not
 3359        // on existing delimiter.
 3360        cx.set_state(indoc! {"
 3361        /**
 3362 3363         */
 3364    "});
 3365        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3366        cx.assert_editor_state(indoc! {"
 3367        /**
 3368         *
 3369         * ˇ
 3370         */
 3371    "});
 3372        // Ensure that if suffix exists on same line after cursor it
 3373        // doesn't add extra new line if prefix is not on same line.
 3374        cx.set_state(indoc! {"
 3375        /**
 3376        ˇ*/
 3377    "});
 3378        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3379        cx.assert_editor_state(indoc! {"
 3380        /**
 3381
 3382        ˇ*/
 3383    "});
 3384        // Ensure that it detects suffix after existing prefix.
 3385        cx.set_state(indoc! {"
 3386        /**ˇ/
 3387    "});
 3388        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3389        cx.assert_editor_state(indoc! {"
 3390        /**
 3391        ˇ/
 3392    "});
 3393        // Ensure that if suffix exists on same line before
 3394        // cursor it does not add comment prefix.
 3395        cx.set_state(indoc! {"
 3396        /** */ˇ
 3397    "});
 3398        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3399        cx.assert_editor_state(indoc! {"
 3400        /** */
 3401        ˇ
 3402    "});
 3403        // Ensure that if suffix exists on same line before
 3404        // cursor it does not add comment prefix.
 3405        cx.set_state(indoc! {"
 3406        /**
 3407         *
 3408         */ˇ
 3409    "});
 3410        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3411        cx.assert_editor_state(indoc! {"
 3412        /**
 3413         *
 3414         */
 3415         ˇ
 3416    "});
 3417
 3418        // Ensure that inline comment followed by code
 3419        // doesn't add comment prefix on newline
 3420        cx.set_state(indoc! {"
 3421        /** */ textˇ
 3422    "});
 3423        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3424        cx.assert_editor_state(indoc! {"
 3425        /** */ text
 3426        ˇ
 3427    "});
 3428
 3429        // Ensure that text after comment end tag
 3430        // doesn't add comment prefix on newline
 3431        cx.set_state(indoc! {"
 3432        /**
 3433         *
 3434         */ˇtext
 3435    "});
 3436        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3437        cx.assert_editor_state(indoc! {"
 3438        /**
 3439         *
 3440         */
 3441         ˇtext
 3442    "});
 3443
 3444        // Ensure if not comment block it doesn't
 3445        // add comment prefix on newline
 3446        cx.set_state(indoc! {"
 3447        * textˇ
 3448    "});
 3449        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3450        cx.assert_editor_state(indoc! {"
 3451        * text
 3452        ˇ
 3453    "});
 3454    }
 3455    // Ensure that comment continuations can be disabled.
 3456    update_test_language_settings(cx, |settings| {
 3457        settings.defaults.extend_comment_on_newline = Some(false);
 3458    });
 3459    let mut cx = EditorTestContext::new(cx).await;
 3460    cx.set_state(indoc! {"
 3461        /**ˇ
 3462    "});
 3463    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3464    cx.assert_editor_state(indoc! {"
 3465        /**
 3466        ˇ
 3467    "});
 3468}
 3469
 3470#[gpui::test]
 3471async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3472    init_test(cx, |settings| {
 3473        settings.defaults.tab_size = NonZeroU32::new(4)
 3474    });
 3475
 3476    let lua_language = Arc::new(Language::new(
 3477        LanguageConfig {
 3478            line_comments: vec!["--".into()],
 3479            block_comment: Some(language::BlockCommentConfig {
 3480                start: "--[[".into(),
 3481                prefix: "".into(),
 3482                end: "]]".into(),
 3483                tab_size: 0,
 3484            }),
 3485            ..LanguageConfig::default()
 3486        },
 3487        None,
 3488    ));
 3489
 3490    let mut cx = EditorTestContext::new(cx).await;
 3491    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3492
 3493    // Line with line comment should extend
 3494    cx.set_state(indoc! {"
 3495        --ˇ
 3496    "});
 3497    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3498    cx.assert_editor_state(indoc! {"
 3499        --
 3500        --ˇ
 3501    "});
 3502
 3503    // Line with block comment that matches line comment should not extend
 3504    cx.set_state(indoc! {"
 3505        --[[ˇ
 3506    "});
 3507    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3508    cx.assert_editor_state(indoc! {"
 3509        --[[
 3510        ˇ
 3511    "});
 3512}
 3513
 3514#[gpui::test]
 3515fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3516    init_test(cx, |_| {});
 3517
 3518    let editor = cx.add_window(|window, cx| {
 3519        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3520        let mut editor = build_editor(buffer, window, cx);
 3521        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3522            s.select_ranges([3..4, 11..12, 19..20])
 3523        });
 3524        editor
 3525    });
 3526
 3527    _ = editor.update(cx, |editor, window, cx| {
 3528        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3529        editor.buffer.update(cx, |buffer, cx| {
 3530            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3531            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3532        });
 3533        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3534
 3535        editor.insert("Z", window, cx);
 3536        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3537
 3538        // The selections are moved after the inserted characters
 3539        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3540    });
 3541}
 3542
 3543#[gpui::test]
 3544async fn test_tab(cx: &mut TestAppContext) {
 3545    init_test(cx, |settings| {
 3546        settings.defaults.tab_size = NonZeroU32::new(3)
 3547    });
 3548
 3549    let mut cx = EditorTestContext::new(cx).await;
 3550    cx.set_state(indoc! {"
 3551        ˇabˇc
 3552        ˇ🏀ˇ🏀ˇefg
 3553 3554    "});
 3555    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3556    cx.assert_editor_state(indoc! {"
 3557           ˇab ˇc
 3558           ˇ🏀  ˇ🏀  ˇefg
 3559        d  ˇ
 3560    "});
 3561
 3562    cx.set_state(indoc! {"
 3563        a
 3564        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3565    "});
 3566    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3567    cx.assert_editor_state(indoc! {"
 3568        a
 3569           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3570    "});
 3571}
 3572
 3573#[gpui::test]
 3574async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3575    init_test(cx, |_| {});
 3576
 3577    let mut cx = EditorTestContext::new(cx).await;
 3578    let language = Arc::new(
 3579        Language::new(
 3580            LanguageConfig::default(),
 3581            Some(tree_sitter_rust::LANGUAGE.into()),
 3582        )
 3583        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3584        .unwrap(),
 3585    );
 3586    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3587
 3588    // test when all cursors are not at suggested indent
 3589    // then simply move to their suggested indent location
 3590    cx.set_state(indoc! {"
 3591        const a: B = (
 3592            c(
 3593        ˇ
 3594        ˇ    )
 3595        );
 3596    "});
 3597    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3598    cx.assert_editor_state(indoc! {"
 3599        const a: B = (
 3600            c(
 3601                ˇ
 3602            ˇ)
 3603        );
 3604    "});
 3605
 3606    // test cursor already at suggested indent not moving when
 3607    // other cursors are yet to reach their suggested indents
 3608    cx.set_state(indoc! {"
 3609        ˇ
 3610        const a: B = (
 3611            c(
 3612                d(
 3613        ˇ
 3614                )
 3615        ˇ
 3616        ˇ    )
 3617        );
 3618    "});
 3619    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3620    cx.assert_editor_state(indoc! {"
 3621        ˇ
 3622        const a: B = (
 3623            c(
 3624                d(
 3625                    ˇ
 3626                )
 3627                ˇ
 3628            ˇ)
 3629        );
 3630    "});
 3631    // test when all cursors are at suggested indent then tab is inserted
 3632    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3633    cx.assert_editor_state(indoc! {"
 3634            ˇ
 3635        const a: B = (
 3636            c(
 3637                d(
 3638                        ˇ
 3639                )
 3640                    ˇ
 3641                ˇ)
 3642        );
 3643    "});
 3644
 3645    // test when current indent is less than suggested indent,
 3646    // we adjust line to match suggested indent and move cursor to it
 3647    //
 3648    // when no other cursor is at word boundary, all of them should move
 3649    cx.set_state(indoc! {"
 3650        const a: B = (
 3651            c(
 3652                d(
 3653        ˇ
 3654        ˇ   )
 3655        ˇ   )
 3656        );
 3657    "});
 3658    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3659    cx.assert_editor_state(indoc! {"
 3660        const a: B = (
 3661            c(
 3662                d(
 3663                    ˇ
 3664                ˇ)
 3665            ˇ)
 3666        );
 3667    "});
 3668
 3669    // test when current indent is less than suggested indent,
 3670    // we adjust line to match suggested indent and move cursor to it
 3671    //
 3672    // when some other cursor is at word boundary, it should not move
 3673    cx.set_state(indoc! {"
 3674        const a: B = (
 3675            c(
 3676                d(
 3677        ˇ
 3678        ˇ   )
 3679           ˇ)
 3680        );
 3681    "});
 3682    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3683    cx.assert_editor_state(indoc! {"
 3684        const a: B = (
 3685            c(
 3686                d(
 3687                    ˇ
 3688                ˇ)
 3689            ˇ)
 3690        );
 3691    "});
 3692
 3693    // test when current indent is more than suggested indent,
 3694    // we just move cursor to current indent instead of suggested indent
 3695    //
 3696    // when no other cursor is at word boundary, all of them should move
 3697    cx.set_state(indoc! {"
 3698        const a: B = (
 3699            c(
 3700                d(
 3701        ˇ
 3702        ˇ                )
 3703        ˇ   )
 3704        );
 3705    "});
 3706    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3707    cx.assert_editor_state(indoc! {"
 3708        const a: B = (
 3709            c(
 3710                d(
 3711                    ˇ
 3712                        ˇ)
 3713            ˇ)
 3714        );
 3715    "});
 3716    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3717    cx.assert_editor_state(indoc! {"
 3718        const a: B = (
 3719            c(
 3720                d(
 3721                        ˇ
 3722                            ˇ)
 3723                ˇ)
 3724        );
 3725    "});
 3726
 3727    // test when current indent is more than suggested indent,
 3728    // we just move cursor to current indent instead of suggested indent
 3729    //
 3730    // when some other cursor is at word boundary, it doesn't move
 3731    cx.set_state(indoc! {"
 3732        const a: B = (
 3733            c(
 3734                d(
 3735        ˇ
 3736        ˇ                )
 3737            ˇ)
 3738        );
 3739    "});
 3740    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3741    cx.assert_editor_state(indoc! {"
 3742        const a: B = (
 3743            c(
 3744                d(
 3745                    ˇ
 3746                        ˇ)
 3747            ˇ)
 3748        );
 3749    "});
 3750
 3751    // handle auto-indent when there are multiple cursors on the same line
 3752    cx.set_state(indoc! {"
 3753        const a: B = (
 3754            c(
 3755        ˇ    ˇ
 3756        ˇ    )
 3757        );
 3758    "});
 3759    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3760    cx.assert_editor_state(indoc! {"
 3761        const a: B = (
 3762            c(
 3763                ˇ
 3764            ˇ)
 3765        );
 3766    "});
 3767}
 3768
 3769#[gpui::test]
 3770async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3771    init_test(cx, |settings| {
 3772        settings.defaults.tab_size = NonZeroU32::new(3)
 3773    });
 3774
 3775    let mut cx = EditorTestContext::new(cx).await;
 3776    cx.set_state(indoc! {"
 3777         ˇ
 3778        \t ˇ
 3779        \t  ˇ
 3780        \t   ˇ
 3781         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3782    "});
 3783
 3784    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3785    cx.assert_editor_state(indoc! {"
 3786           ˇ
 3787        \t   ˇ
 3788        \t   ˇ
 3789        \t      ˇ
 3790         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3791    "});
 3792}
 3793
 3794#[gpui::test]
 3795async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3796    init_test(cx, |settings| {
 3797        settings.defaults.tab_size = NonZeroU32::new(4)
 3798    });
 3799
 3800    let language = Arc::new(
 3801        Language::new(
 3802            LanguageConfig::default(),
 3803            Some(tree_sitter_rust::LANGUAGE.into()),
 3804        )
 3805        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3806        .unwrap(),
 3807    );
 3808
 3809    let mut cx = EditorTestContext::new(cx).await;
 3810    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3811    cx.set_state(indoc! {"
 3812        fn a() {
 3813            if b {
 3814        \t ˇc
 3815            }
 3816        }
 3817    "});
 3818
 3819    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3820    cx.assert_editor_state(indoc! {"
 3821        fn a() {
 3822            if b {
 3823                ˇc
 3824            }
 3825        }
 3826    "});
 3827}
 3828
 3829#[gpui::test]
 3830async fn test_indent_outdent(cx: &mut TestAppContext) {
 3831    init_test(cx, |settings| {
 3832        settings.defaults.tab_size = NonZeroU32::new(4);
 3833    });
 3834
 3835    let mut cx = EditorTestContext::new(cx).await;
 3836
 3837    cx.set_state(indoc! {"
 3838          «oneˇ» «twoˇ»
 3839        three
 3840         four
 3841    "});
 3842    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3843    cx.assert_editor_state(indoc! {"
 3844            «oneˇ» «twoˇ»
 3845        three
 3846         four
 3847    "});
 3848
 3849    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3850    cx.assert_editor_state(indoc! {"
 3851        «oneˇ» «twoˇ»
 3852        three
 3853         four
 3854    "});
 3855
 3856    // select across line ending
 3857    cx.set_state(indoc! {"
 3858        one two
 3859        t«hree
 3860        ˇ» four
 3861    "});
 3862    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3863    cx.assert_editor_state(indoc! {"
 3864        one two
 3865            t«hree
 3866        ˇ» four
 3867    "});
 3868
 3869    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3870    cx.assert_editor_state(indoc! {"
 3871        one two
 3872        t«hree
 3873        ˇ» four
 3874    "});
 3875
 3876    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3877    cx.set_state(indoc! {"
 3878        one two
 3879        ˇthree
 3880            four
 3881    "});
 3882    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3883    cx.assert_editor_state(indoc! {"
 3884        one two
 3885            ˇthree
 3886            four
 3887    "});
 3888
 3889    cx.set_state(indoc! {"
 3890        one two
 3891        ˇ    three
 3892            four
 3893    "});
 3894    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3895    cx.assert_editor_state(indoc! {"
 3896        one two
 3897        ˇthree
 3898            four
 3899    "});
 3900}
 3901
 3902#[gpui::test]
 3903async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3904    // This is a regression test for issue #33761
 3905    init_test(cx, |_| {});
 3906
 3907    let mut cx = EditorTestContext::new(cx).await;
 3908    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3909    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3910
 3911    cx.set_state(
 3912        r#"ˇ#     ingress:
 3913ˇ#         api:
 3914ˇ#             enabled: false
 3915ˇ#             pathType: Prefix
 3916ˇ#           console:
 3917ˇ#               enabled: false
 3918ˇ#               pathType: Prefix
 3919"#,
 3920    );
 3921
 3922    // Press tab to indent all lines
 3923    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3924
 3925    cx.assert_editor_state(
 3926        r#"    ˇ#     ingress:
 3927    ˇ#         api:
 3928    ˇ#             enabled: false
 3929    ˇ#             pathType: Prefix
 3930    ˇ#           console:
 3931    ˇ#               enabled: false
 3932    ˇ#               pathType: Prefix
 3933"#,
 3934    );
 3935}
 3936
 3937#[gpui::test]
 3938async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3939    // This is a test to make sure our fix for issue #33761 didn't break anything
 3940    init_test(cx, |_| {});
 3941
 3942    let mut cx = EditorTestContext::new(cx).await;
 3943    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3944    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3945
 3946    cx.set_state(
 3947        r#"ˇingress:
 3948ˇ  api:
 3949ˇ    enabled: false
 3950ˇ    pathType: Prefix
 3951"#,
 3952    );
 3953
 3954    // Press tab to indent all lines
 3955    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3956
 3957    cx.assert_editor_state(
 3958        r#"ˇingress:
 3959    ˇapi:
 3960        ˇenabled: false
 3961        ˇpathType: Prefix
 3962"#,
 3963    );
 3964}
 3965
 3966#[gpui::test]
 3967async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3968    init_test(cx, |settings| {
 3969        settings.defaults.hard_tabs = Some(true);
 3970    });
 3971
 3972    let mut cx = EditorTestContext::new(cx).await;
 3973
 3974    // select two ranges on one line
 3975    cx.set_state(indoc! {"
 3976        «oneˇ» «twoˇ»
 3977        three
 3978        four
 3979    "});
 3980    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3981    cx.assert_editor_state(indoc! {"
 3982        \t«oneˇ» «twoˇ»
 3983        three
 3984        four
 3985    "});
 3986    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3987    cx.assert_editor_state(indoc! {"
 3988        \t\t«oneˇ» «twoˇ»
 3989        three
 3990        four
 3991    "});
 3992    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3993    cx.assert_editor_state(indoc! {"
 3994        \t«oneˇ» «twoˇ»
 3995        three
 3996        four
 3997    "});
 3998    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3999    cx.assert_editor_state(indoc! {"
 4000        «oneˇ» «twoˇ»
 4001        three
 4002        four
 4003    "});
 4004
 4005    // select across a line ending
 4006    cx.set_state(indoc! {"
 4007        one two
 4008        t«hree
 4009        ˇ»four
 4010    "});
 4011    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4012    cx.assert_editor_state(indoc! {"
 4013        one two
 4014        \tt«hree
 4015        ˇ»four
 4016    "});
 4017    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4018    cx.assert_editor_state(indoc! {"
 4019        one two
 4020        \t\tt«hree
 4021        ˇ»four
 4022    "});
 4023    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4024    cx.assert_editor_state(indoc! {"
 4025        one two
 4026        \tt«hree
 4027        ˇ»four
 4028    "});
 4029    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4030    cx.assert_editor_state(indoc! {"
 4031        one two
 4032        t«hree
 4033        ˇ»four
 4034    "});
 4035
 4036    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4037    cx.set_state(indoc! {"
 4038        one two
 4039        ˇthree
 4040        four
 4041    "});
 4042    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4043    cx.assert_editor_state(indoc! {"
 4044        one two
 4045        ˇthree
 4046        four
 4047    "});
 4048    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4049    cx.assert_editor_state(indoc! {"
 4050        one two
 4051        \tˇthree
 4052        four
 4053    "});
 4054    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4055    cx.assert_editor_state(indoc! {"
 4056        one two
 4057        ˇthree
 4058        four
 4059    "});
 4060}
 4061
 4062#[gpui::test]
 4063fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4064    init_test(cx, |settings| {
 4065        settings.languages.0.extend([
 4066            (
 4067                "TOML".into(),
 4068                LanguageSettingsContent {
 4069                    tab_size: NonZeroU32::new(2),
 4070                    ..Default::default()
 4071                },
 4072            ),
 4073            (
 4074                "Rust".into(),
 4075                LanguageSettingsContent {
 4076                    tab_size: NonZeroU32::new(4),
 4077                    ..Default::default()
 4078                },
 4079            ),
 4080        ]);
 4081    });
 4082
 4083    let toml_language = Arc::new(Language::new(
 4084        LanguageConfig {
 4085            name: "TOML".into(),
 4086            ..Default::default()
 4087        },
 4088        None,
 4089    ));
 4090    let rust_language = Arc::new(Language::new(
 4091        LanguageConfig {
 4092            name: "Rust".into(),
 4093            ..Default::default()
 4094        },
 4095        None,
 4096    ));
 4097
 4098    let toml_buffer =
 4099        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4100    let rust_buffer =
 4101        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4102    let multibuffer = cx.new(|cx| {
 4103        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4104        multibuffer.push_excerpts(
 4105            toml_buffer.clone(),
 4106            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4107            cx,
 4108        );
 4109        multibuffer.push_excerpts(
 4110            rust_buffer.clone(),
 4111            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4112            cx,
 4113        );
 4114        multibuffer
 4115    });
 4116
 4117    cx.add_window(|window, cx| {
 4118        let mut editor = build_editor(multibuffer, window, cx);
 4119
 4120        assert_eq!(
 4121            editor.text(cx),
 4122            indoc! {"
 4123                a = 1
 4124                b = 2
 4125
 4126                const c: usize = 3;
 4127            "}
 4128        );
 4129
 4130        select_ranges(
 4131            &mut editor,
 4132            indoc! {"
 4133                «aˇ» = 1
 4134                b = 2
 4135
 4136                «const c:ˇ» usize = 3;
 4137            "},
 4138            window,
 4139            cx,
 4140        );
 4141
 4142        editor.tab(&Tab, window, cx);
 4143        assert_text_with_selections(
 4144            &mut editor,
 4145            indoc! {"
 4146                  «aˇ» = 1
 4147                b = 2
 4148
 4149                    «const c:ˇ» usize = 3;
 4150            "},
 4151            cx,
 4152        );
 4153        editor.backtab(&Backtab, window, cx);
 4154        assert_text_with_selections(
 4155            &mut editor,
 4156            indoc! {"
 4157                «aˇ» = 1
 4158                b = 2
 4159
 4160                «const c:ˇ» usize = 3;
 4161            "},
 4162            cx,
 4163        );
 4164
 4165        editor
 4166    });
 4167}
 4168
 4169#[gpui::test]
 4170async fn test_backspace(cx: &mut TestAppContext) {
 4171    init_test(cx, |_| {});
 4172
 4173    let mut cx = EditorTestContext::new(cx).await;
 4174
 4175    // Basic backspace
 4176    cx.set_state(indoc! {"
 4177        onˇe two three
 4178        fou«rˇ» five six
 4179        seven «ˇeight nine
 4180        »ten
 4181    "});
 4182    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4183    cx.assert_editor_state(indoc! {"
 4184        oˇe two three
 4185        fouˇ five six
 4186        seven ˇten
 4187    "});
 4188
 4189    // Test backspace inside and around indents
 4190    cx.set_state(indoc! {"
 4191        zero
 4192            ˇone
 4193                ˇtwo
 4194            ˇ ˇ ˇ  three
 4195        ˇ  ˇ  four
 4196    "});
 4197    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4198    cx.assert_editor_state(indoc! {"
 4199        zero
 4200        ˇone
 4201            ˇtwo
 4202        ˇ  threeˇ  four
 4203    "});
 4204}
 4205
 4206#[gpui::test]
 4207async fn test_delete(cx: &mut TestAppContext) {
 4208    init_test(cx, |_| {});
 4209
 4210    let mut cx = EditorTestContext::new(cx).await;
 4211    cx.set_state(indoc! {"
 4212        onˇe two three
 4213        fou«rˇ» five six
 4214        seven «ˇeight nine
 4215        »ten
 4216    "});
 4217    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4218    cx.assert_editor_state(indoc! {"
 4219        onˇ two three
 4220        fouˇ five six
 4221        seven ˇten
 4222    "});
 4223}
 4224
 4225#[gpui::test]
 4226fn test_delete_line(cx: &mut TestAppContext) {
 4227    init_test(cx, |_| {});
 4228
 4229    let editor = cx.add_window(|window, cx| {
 4230        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4231        build_editor(buffer, window, cx)
 4232    });
 4233    _ = editor.update(cx, |editor, window, cx| {
 4234        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4235            s.select_display_ranges([
 4236                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4237                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4238                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4239            ])
 4240        });
 4241        editor.delete_line(&DeleteLine, window, cx);
 4242        assert_eq!(editor.display_text(cx), "ghi");
 4243        assert_eq!(
 4244            editor.selections.display_ranges(cx),
 4245            vec![
 4246                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4247                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 4248            ]
 4249        );
 4250    });
 4251
 4252    let editor = cx.add_window(|window, cx| {
 4253        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4254        build_editor(buffer, window, cx)
 4255    });
 4256    _ = editor.update(cx, |editor, window, cx| {
 4257        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4258            s.select_display_ranges([
 4259                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4260            ])
 4261        });
 4262        editor.delete_line(&DeleteLine, window, cx);
 4263        assert_eq!(editor.display_text(cx), "ghi\n");
 4264        assert_eq!(
 4265            editor.selections.display_ranges(cx),
 4266            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4267        );
 4268    });
 4269}
 4270
 4271#[gpui::test]
 4272fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4273    init_test(cx, |_| {});
 4274
 4275    cx.add_window(|window, cx| {
 4276        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4277        let mut editor = build_editor(buffer.clone(), window, cx);
 4278        let buffer = buffer.read(cx).as_singleton().unwrap();
 4279
 4280        assert_eq!(
 4281            editor.selections.ranges::<Point>(cx),
 4282            &[Point::new(0, 0)..Point::new(0, 0)]
 4283        );
 4284
 4285        // When on single line, replace newline at end by space
 4286        editor.join_lines(&JoinLines, window, cx);
 4287        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4288        assert_eq!(
 4289            editor.selections.ranges::<Point>(cx),
 4290            &[Point::new(0, 3)..Point::new(0, 3)]
 4291        );
 4292
 4293        // When multiple lines are selected, remove newlines that are spanned by the selection
 4294        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4295            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4296        });
 4297        editor.join_lines(&JoinLines, window, cx);
 4298        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4299        assert_eq!(
 4300            editor.selections.ranges::<Point>(cx),
 4301            &[Point::new(0, 11)..Point::new(0, 11)]
 4302        );
 4303
 4304        // Undo should be transactional
 4305        editor.undo(&Undo, window, cx);
 4306        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4307        assert_eq!(
 4308            editor.selections.ranges::<Point>(cx),
 4309            &[Point::new(0, 5)..Point::new(2, 2)]
 4310        );
 4311
 4312        // When joining an empty line don't insert a space
 4313        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4314            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4315        });
 4316        editor.join_lines(&JoinLines, window, cx);
 4317        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4318        assert_eq!(
 4319            editor.selections.ranges::<Point>(cx),
 4320            [Point::new(2, 3)..Point::new(2, 3)]
 4321        );
 4322
 4323        // We can remove trailing newlines
 4324        editor.join_lines(&JoinLines, window, cx);
 4325        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4326        assert_eq!(
 4327            editor.selections.ranges::<Point>(cx),
 4328            [Point::new(2, 3)..Point::new(2, 3)]
 4329        );
 4330
 4331        // We don't blow up on the last line
 4332        editor.join_lines(&JoinLines, window, cx);
 4333        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4334        assert_eq!(
 4335            editor.selections.ranges::<Point>(cx),
 4336            [Point::new(2, 3)..Point::new(2, 3)]
 4337        );
 4338
 4339        // reset to test indentation
 4340        editor.buffer.update(cx, |buffer, cx| {
 4341            buffer.edit(
 4342                [
 4343                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4344                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4345                ],
 4346                None,
 4347                cx,
 4348            )
 4349        });
 4350
 4351        // We remove any leading spaces
 4352        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4353        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4354            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4355        });
 4356        editor.join_lines(&JoinLines, window, cx);
 4357        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4358
 4359        // We don't insert a space for a line containing only spaces
 4360        editor.join_lines(&JoinLines, window, cx);
 4361        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4362
 4363        // We ignore any leading tabs
 4364        editor.join_lines(&JoinLines, window, cx);
 4365        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4366
 4367        editor
 4368    });
 4369}
 4370
 4371#[gpui::test]
 4372fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4373    init_test(cx, |_| {});
 4374
 4375    cx.add_window(|window, cx| {
 4376        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4377        let mut editor = build_editor(buffer.clone(), window, cx);
 4378        let buffer = buffer.read(cx).as_singleton().unwrap();
 4379
 4380        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4381            s.select_ranges([
 4382                Point::new(0, 2)..Point::new(1, 1),
 4383                Point::new(1, 2)..Point::new(1, 2),
 4384                Point::new(3, 1)..Point::new(3, 2),
 4385            ])
 4386        });
 4387
 4388        editor.join_lines(&JoinLines, window, cx);
 4389        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4390
 4391        assert_eq!(
 4392            editor.selections.ranges::<Point>(cx),
 4393            [
 4394                Point::new(0, 7)..Point::new(0, 7),
 4395                Point::new(1, 3)..Point::new(1, 3)
 4396            ]
 4397        );
 4398        editor
 4399    });
 4400}
 4401
 4402#[gpui::test]
 4403async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4404    init_test(cx, |_| {});
 4405
 4406    let mut cx = EditorTestContext::new(cx).await;
 4407
 4408    let diff_base = r#"
 4409        Line 0
 4410        Line 1
 4411        Line 2
 4412        Line 3
 4413        "#
 4414    .unindent();
 4415
 4416    cx.set_state(
 4417        &r#"
 4418        ˇLine 0
 4419        Line 1
 4420        Line 2
 4421        Line 3
 4422        "#
 4423        .unindent(),
 4424    );
 4425
 4426    cx.set_head_text(&diff_base);
 4427    executor.run_until_parked();
 4428
 4429    // Join lines
 4430    cx.update_editor(|editor, window, cx| {
 4431        editor.join_lines(&JoinLines, window, cx);
 4432    });
 4433    executor.run_until_parked();
 4434
 4435    cx.assert_editor_state(
 4436        &r#"
 4437        Line 0ˇ Line 1
 4438        Line 2
 4439        Line 3
 4440        "#
 4441        .unindent(),
 4442    );
 4443    // Join again
 4444    cx.update_editor(|editor, window, cx| {
 4445        editor.join_lines(&JoinLines, window, cx);
 4446    });
 4447    executor.run_until_parked();
 4448
 4449    cx.assert_editor_state(
 4450        &r#"
 4451        Line 0 Line 1ˇ Line 2
 4452        Line 3
 4453        "#
 4454        .unindent(),
 4455    );
 4456}
 4457
 4458#[gpui::test]
 4459async fn test_custom_newlines_cause_no_false_positive_diffs(
 4460    executor: BackgroundExecutor,
 4461    cx: &mut TestAppContext,
 4462) {
 4463    init_test(cx, |_| {});
 4464    let mut cx = EditorTestContext::new(cx).await;
 4465    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4466    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4467    executor.run_until_parked();
 4468
 4469    cx.update_editor(|editor, window, cx| {
 4470        let snapshot = editor.snapshot(window, cx);
 4471        assert_eq!(
 4472            snapshot
 4473                .buffer_snapshot
 4474                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 4475                .collect::<Vec<_>>(),
 4476            Vec::new(),
 4477            "Should not have any diffs for files with custom newlines"
 4478        );
 4479    });
 4480}
 4481
 4482#[gpui::test]
 4483async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4484    init_test(cx, |_| {});
 4485
 4486    let mut cx = EditorTestContext::new(cx).await;
 4487
 4488    // Test sort_lines_case_insensitive()
 4489    cx.set_state(indoc! {"
 4490        «z
 4491        y
 4492        x
 4493        Z
 4494        Y
 4495        Xˇ»
 4496    "});
 4497    cx.update_editor(|e, window, cx| {
 4498        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4499    });
 4500    cx.assert_editor_state(indoc! {"
 4501        «x
 4502        X
 4503        y
 4504        Y
 4505        z
 4506        Zˇ»
 4507    "});
 4508
 4509    // Test sort_lines_by_length()
 4510    //
 4511    // Demonstrates:
 4512    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4513    // - sort is stable
 4514    cx.set_state(indoc! {"
 4515        «123
 4516        æ
 4517        12
 4518 4519        1
 4520        æˇ»
 4521    "});
 4522    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4523    cx.assert_editor_state(indoc! {"
 4524        «æ
 4525 4526        1
 4527        æ
 4528        12
 4529        123ˇ»
 4530    "});
 4531
 4532    // Test reverse_lines()
 4533    cx.set_state(indoc! {"
 4534        «5
 4535        4
 4536        3
 4537        2
 4538        1ˇ»
 4539    "});
 4540    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4541    cx.assert_editor_state(indoc! {"
 4542        «1
 4543        2
 4544        3
 4545        4
 4546        5ˇ»
 4547    "});
 4548
 4549    // Skip testing shuffle_line()
 4550
 4551    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4552    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4553
 4554    // Don't manipulate when cursor is on single line, but expand the selection
 4555    cx.set_state(indoc! {"
 4556        ddˇdd
 4557        ccc
 4558        bb
 4559        a
 4560    "});
 4561    cx.update_editor(|e, window, cx| {
 4562        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4563    });
 4564    cx.assert_editor_state(indoc! {"
 4565        «ddddˇ»
 4566        ccc
 4567        bb
 4568        a
 4569    "});
 4570
 4571    // Basic manipulate case
 4572    // Start selection moves to column 0
 4573    // End of selection shrinks to fit shorter line
 4574    cx.set_state(indoc! {"
 4575        dd«d
 4576        ccc
 4577        bb
 4578        aaaaaˇ»
 4579    "});
 4580    cx.update_editor(|e, window, cx| {
 4581        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4582    });
 4583    cx.assert_editor_state(indoc! {"
 4584        «aaaaa
 4585        bb
 4586        ccc
 4587        dddˇ»
 4588    "});
 4589
 4590    // Manipulate case with newlines
 4591    cx.set_state(indoc! {"
 4592        dd«d
 4593        ccc
 4594
 4595        bb
 4596        aaaaa
 4597
 4598        ˇ»
 4599    "});
 4600    cx.update_editor(|e, window, cx| {
 4601        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4602    });
 4603    cx.assert_editor_state(indoc! {"
 4604        «
 4605
 4606        aaaaa
 4607        bb
 4608        ccc
 4609        dddˇ»
 4610
 4611    "});
 4612
 4613    // Adding new line
 4614    cx.set_state(indoc! {"
 4615        aa«a
 4616        bbˇ»b
 4617    "});
 4618    cx.update_editor(|e, window, cx| {
 4619        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4620    });
 4621    cx.assert_editor_state(indoc! {"
 4622        «aaa
 4623        bbb
 4624        added_lineˇ»
 4625    "});
 4626
 4627    // Removing line
 4628    cx.set_state(indoc! {"
 4629        aa«a
 4630        bbbˇ»
 4631    "});
 4632    cx.update_editor(|e, window, cx| {
 4633        e.manipulate_immutable_lines(window, cx, |lines| {
 4634            lines.pop();
 4635        })
 4636    });
 4637    cx.assert_editor_state(indoc! {"
 4638        «aaaˇ»
 4639    "});
 4640
 4641    // Removing all lines
 4642    cx.set_state(indoc! {"
 4643        aa«a
 4644        bbbˇ»
 4645    "});
 4646    cx.update_editor(|e, window, cx| {
 4647        e.manipulate_immutable_lines(window, cx, |lines| {
 4648            lines.drain(..);
 4649        })
 4650    });
 4651    cx.assert_editor_state(indoc! {"
 4652        ˇ
 4653    "});
 4654}
 4655
 4656#[gpui::test]
 4657async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4658    init_test(cx, |_| {});
 4659
 4660    let mut cx = EditorTestContext::new(cx).await;
 4661
 4662    // Consider continuous selection as single selection
 4663    cx.set_state(indoc! {"
 4664        Aaa«aa
 4665        cˇ»c«c
 4666        bb
 4667        aaaˇ»aa
 4668    "});
 4669    cx.update_editor(|e, window, cx| {
 4670        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4671    });
 4672    cx.assert_editor_state(indoc! {"
 4673        «Aaaaa
 4674        ccc
 4675        bb
 4676        aaaaaˇ»
 4677    "});
 4678
 4679    cx.set_state(indoc! {"
 4680        Aaa«aa
 4681        cˇ»c«c
 4682        bb
 4683        aaaˇ»aa
 4684    "});
 4685    cx.update_editor(|e, window, cx| {
 4686        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4687    });
 4688    cx.assert_editor_state(indoc! {"
 4689        «Aaaaa
 4690        ccc
 4691        bbˇ»
 4692    "});
 4693
 4694    // Consider non continuous selection as distinct dedup operations
 4695    cx.set_state(indoc! {"
 4696        «aaaaa
 4697        bb
 4698        aaaaa
 4699        aaaaaˇ»
 4700
 4701        aaa«aaˇ»
 4702    "});
 4703    cx.update_editor(|e, window, cx| {
 4704        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4705    });
 4706    cx.assert_editor_state(indoc! {"
 4707        «aaaaa
 4708        bbˇ»
 4709
 4710        «aaaaaˇ»
 4711    "});
 4712}
 4713
 4714#[gpui::test]
 4715async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4716    init_test(cx, |_| {});
 4717
 4718    let mut cx = EditorTestContext::new(cx).await;
 4719
 4720    cx.set_state(indoc! {"
 4721        «Aaa
 4722        aAa
 4723        Aaaˇ»
 4724    "});
 4725    cx.update_editor(|e, window, cx| {
 4726        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4727    });
 4728    cx.assert_editor_state(indoc! {"
 4729        «Aaa
 4730        aAaˇ»
 4731    "});
 4732
 4733    cx.set_state(indoc! {"
 4734        «Aaa
 4735        aAa
 4736        aaAˇ»
 4737    "});
 4738    cx.update_editor(|e, window, cx| {
 4739        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4740    });
 4741    cx.assert_editor_state(indoc! {"
 4742        «Aaaˇ»
 4743    "});
 4744}
 4745
 4746#[gpui::test]
 4747async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 4748    init_test(cx, |_| {});
 4749
 4750    let mut cx = EditorTestContext::new(cx).await;
 4751
 4752    let js_language = Arc::new(Language::new(
 4753        LanguageConfig {
 4754            name: "JavaScript".into(),
 4755            wrap_characters: Some(language::WrapCharactersConfig {
 4756                start_prefix: "<".into(),
 4757                start_suffix: ">".into(),
 4758                end_prefix: "</".into(),
 4759                end_suffix: ">".into(),
 4760            }),
 4761            ..LanguageConfig::default()
 4762        },
 4763        None,
 4764    ));
 4765
 4766    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4767
 4768    cx.set_state(indoc! {"
 4769        «testˇ»
 4770    "});
 4771    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4772    cx.assert_editor_state(indoc! {"
 4773        <«ˇ»>test</«ˇ»>
 4774    "});
 4775
 4776    cx.set_state(indoc! {"
 4777        «test
 4778         testˇ»
 4779    "});
 4780    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4781    cx.assert_editor_state(indoc! {"
 4782        <«ˇ»>test
 4783         test</«ˇ»>
 4784    "});
 4785
 4786    cx.set_state(indoc! {"
 4787        teˇst
 4788    "});
 4789    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4790    cx.assert_editor_state(indoc! {"
 4791        te<«ˇ»></«ˇ»>st
 4792    "});
 4793}
 4794
 4795#[gpui::test]
 4796async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 4797    init_test(cx, |_| {});
 4798
 4799    let mut cx = EditorTestContext::new(cx).await;
 4800
 4801    let js_language = Arc::new(Language::new(
 4802        LanguageConfig {
 4803            name: "JavaScript".into(),
 4804            wrap_characters: Some(language::WrapCharactersConfig {
 4805                start_prefix: "<".into(),
 4806                start_suffix: ">".into(),
 4807                end_prefix: "</".into(),
 4808                end_suffix: ">".into(),
 4809            }),
 4810            ..LanguageConfig::default()
 4811        },
 4812        None,
 4813    ));
 4814
 4815    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4816
 4817    cx.set_state(indoc! {"
 4818        «testˇ»
 4819        «testˇ» «testˇ»
 4820        «testˇ»
 4821    "});
 4822    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4823    cx.assert_editor_state(indoc! {"
 4824        <«ˇ»>test</«ˇ»>
 4825        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 4826        <«ˇ»>test</«ˇ»>
 4827    "});
 4828
 4829    cx.set_state(indoc! {"
 4830        «test
 4831         testˇ»
 4832        «test
 4833         testˇ»
 4834    "});
 4835    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4836    cx.assert_editor_state(indoc! {"
 4837        <«ˇ»>test
 4838         test</«ˇ»>
 4839        <«ˇ»>test
 4840         test</«ˇ»>
 4841    "});
 4842}
 4843
 4844#[gpui::test]
 4845async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 4846    init_test(cx, |_| {});
 4847
 4848    let mut cx = EditorTestContext::new(cx).await;
 4849
 4850    let plaintext_language = Arc::new(Language::new(
 4851        LanguageConfig {
 4852            name: "Plain Text".into(),
 4853            ..LanguageConfig::default()
 4854        },
 4855        None,
 4856    ));
 4857
 4858    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 4859
 4860    cx.set_state(indoc! {"
 4861        «testˇ»
 4862    "});
 4863    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4864    cx.assert_editor_state(indoc! {"
 4865      «testˇ»
 4866    "});
 4867}
 4868
 4869#[gpui::test]
 4870async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4871    init_test(cx, |_| {});
 4872
 4873    let mut cx = EditorTestContext::new(cx).await;
 4874
 4875    // Manipulate with multiple selections on a single line
 4876    cx.set_state(indoc! {"
 4877        dd«dd
 4878        cˇ»c«c
 4879        bb
 4880        aaaˇ»aa
 4881    "});
 4882    cx.update_editor(|e, window, cx| {
 4883        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4884    });
 4885    cx.assert_editor_state(indoc! {"
 4886        «aaaaa
 4887        bb
 4888        ccc
 4889        ddddˇ»
 4890    "});
 4891
 4892    // Manipulate with multiple disjoin selections
 4893    cx.set_state(indoc! {"
 4894 4895        4
 4896        3
 4897        2
 4898        1ˇ»
 4899
 4900        dd«dd
 4901        ccc
 4902        bb
 4903        aaaˇ»aa
 4904    "});
 4905    cx.update_editor(|e, window, cx| {
 4906        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4907    });
 4908    cx.assert_editor_state(indoc! {"
 4909        «1
 4910        2
 4911        3
 4912        4
 4913        5ˇ»
 4914
 4915        «aaaaa
 4916        bb
 4917        ccc
 4918        ddddˇ»
 4919    "});
 4920
 4921    // Adding lines on each selection
 4922    cx.set_state(indoc! {"
 4923 4924        1ˇ»
 4925
 4926        bb«bb
 4927        aaaˇ»aa
 4928    "});
 4929    cx.update_editor(|e, window, cx| {
 4930        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4931    });
 4932    cx.assert_editor_state(indoc! {"
 4933        «2
 4934        1
 4935        added lineˇ»
 4936
 4937        «bbbb
 4938        aaaaa
 4939        added lineˇ»
 4940    "});
 4941
 4942    // Removing lines on each selection
 4943    cx.set_state(indoc! {"
 4944 4945        1ˇ»
 4946
 4947        bb«bb
 4948        aaaˇ»aa
 4949    "});
 4950    cx.update_editor(|e, window, cx| {
 4951        e.manipulate_immutable_lines(window, cx, |lines| {
 4952            lines.pop();
 4953        })
 4954    });
 4955    cx.assert_editor_state(indoc! {"
 4956        «2ˇ»
 4957
 4958        «bbbbˇ»
 4959    "});
 4960}
 4961
 4962#[gpui::test]
 4963async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 4964    init_test(cx, |settings| {
 4965        settings.defaults.tab_size = NonZeroU32::new(3)
 4966    });
 4967
 4968    let mut cx = EditorTestContext::new(cx).await;
 4969
 4970    // MULTI SELECTION
 4971    // Ln.1 "«" tests empty lines
 4972    // Ln.9 tests just leading whitespace
 4973    cx.set_state(indoc! {"
 4974        «
 4975        abc                 // No indentationˇ»
 4976        «\tabc              // 1 tabˇ»
 4977        \t\tabc «      ˇ»   // 2 tabs
 4978        \t ab«c             // Tab followed by space
 4979         \tabc              // Space followed by tab (3 spaces should be the result)
 4980        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4981           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 4982        \t
 4983        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4984    "});
 4985    cx.update_editor(|e, window, cx| {
 4986        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4987    });
 4988    cx.assert_editor_state(
 4989        indoc! {"
 4990            «
 4991            abc                 // No indentation
 4992               abc              // 1 tab
 4993                  abc          // 2 tabs
 4994                abc             // Tab followed by space
 4995               abc              // Space followed by tab (3 spaces should be the result)
 4996                           abc   // Mixed indentation (tab conversion depends on the column)
 4997               abc         // Already space indented
 4998               ·
 4999               abc\tdef          // Only the leading tab is manipulatedˇ»
 5000        "}
 5001        .replace("·", "")
 5002        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5003    );
 5004
 5005    // Test on just a few lines, the others should remain unchanged
 5006    // Only lines (3, 5, 10, 11) should change
 5007    cx.set_state(
 5008        indoc! {"
 5009            ·
 5010            abc                 // No indentation
 5011            \tabcˇ               // 1 tab
 5012            \t\tabc             // 2 tabs
 5013            \t abcˇ              // Tab followed by space
 5014             \tabc              // Space followed by tab (3 spaces should be the result)
 5015            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5016               abc              // Already space indented
 5017            «\t
 5018            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5019        "}
 5020        .replace("·", "")
 5021        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5022    );
 5023    cx.update_editor(|e, window, cx| {
 5024        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5025    });
 5026    cx.assert_editor_state(
 5027        indoc! {"
 5028            ·
 5029            abc                 // No indentation
 5030            «   abc               // 1 tabˇ»
 5031            \t\tabc             // 2 tabs
 5032            «    abc              // Tab followed by spaceˇ»
 5033             \tabc              // Space followed by tab (3 spaces should be the result)
 5034            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5035               abc              // Already space indented
 5036            «   ·
 5037               abc\tdef          // Only the leading tab is manipulatedˇ»
 5038        "}
 5039        .replace("·", "")
 5040        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5041    );
 5042
 5043    // SINGLE SELECTION
 5044    // Ln.1 "«" tests empty lines
 5045    // Ln.9 tests just leading whitespace
 5046    cx.set_state(indoc! {"
 5047        «
 5048        abc                 // No indentation
 5049        \tabc               // 1 tab
 5050        \t\tabc             // 2 tabs
 5051        \t abc              // Tab followed by space
 5052         \tabc              // Space followed by tab (3 spaces should be the result)
 5053        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5054           abc              // Already space indented
 5055        \t
 5056        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5057    "});
 5058    cx.update_editor(|e, window, cx| {
 5059        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5060    });
 5061    cx.assert_editor_state(
 5062        indoc! {"
 5063            «
 5064            abc                 // No indentation
 5065               abc               // 1 tab
 5066                  abc             // 2 tabs
 5067                abc              // Tab followed by space
 5068               abc              // Space followed by tab (3 spaces should be the result)
 5069                           abc   // Mixed indentation (tab conversion depends on the column)
 5070               abc              // Already space indented
 5071               ·
 5072               abc\tdef          // Only the leading tab is manipulatedˇ»
 5073        "}
 5074        .replace("·", "")
 5075        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5076    );
 5077}
 5078
 5079#[gpui::test]
 5080async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5081    init_test(cx, |settings| {
 5082        settings.defaults.tab_size = NonZeroU32::new(3)
 5083    });
 5084
 5085    let mut cx = EditorTestContext::new(cx).await;
 5086
 5087    // MULTI SELECTION
 5088    // Ln.1 "«" tests empty lines
 5089    // Ln.11 tests just leading whitespace
 5090    cx.set_state(indoc! {"
 5091        «
 5092        abˇ»ˇc                 // No indentation
 5093         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5094          abc  «             // 2 spaces (< 3 so dont convert)
 5095           abc              // 3 spaces (convert)
 5096             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5097        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5098        «\t abc              // Tab followed by space
 5099         \tabc              // Space followed by tab (should be consumed due to tab)
 5100        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5101           \tˇ»  «\t
 5102           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5103    "});
 5104    cx.update_editor(|e, window, cx| {
 5105        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5106    });
 5107    cx.assert_editor_state(indoc! {"
 5108        «
 5109        abc                 // No indentation
 5110         abc                // 1 space (< 3 so dont convert)
 5111          abc               // 2 spaces (< 3 so dont convert)
 5112        \tabc              // 3 spaces (convert)
 5113        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5114        \t\t\tabc           // Already tab indented
 5115        \t abc              // Tab followed by space
 5116        \tabc              // Space followed by tab (should be consumed due to tab)
 5117        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5118        \t\t\t
 5119        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5120    "});
 5121
 5122    // Test on just a few lines, the other should remain unchanged
 5123    // Only lines (4, 8, 11, 12) should change
 5124    cx.set_state(
 5125        indoc! {"
 5126            ·
 5127            abc                 // No indentation
 5128             abc                // 1 space (< 3 so dont convert)
 5129              abc               // 2 spaces (< 3 so dont convert)
 5130            «   abc              // 3 spaces (convert)ˇ»
 5131                 abc            // 5 spaces (1 tab + 2 spaces)
 5132            \t\t\tabc           // Already tab indented
 5133            \t abc              // Tab followed by space
 5134             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5135               \t\t  \tabc      // Mixed indentation
 5136            \t \t  \t   \tabc   // Mixed indentation
 5137               \t  \tˇ
 5138            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5139        "}
 5140        .replace("·", "")
 5141        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5142    );
 5143    cx.update_editor(|e, window, cx| {
 5144        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5145    });
 5146    cx.assert_editor_state(
 5147        indoc! {"
 5148            ·
 5149            abc                 // No indentation
 5150             abc                // 1 space (< 3 so dont convert)
 5151              abc               // 2 spaces (< 3 so dont convert)
 5152            «\tabc              // 3 spaces (convert)ˇ»
 5153                 abc            // 5 spaces (1 tab + 2 spaces)
 5154            \t\t\tabc           // Already tab indented
 5155            \t abc              // Tab followed by space
 5156            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5157               \t\t  \tabc      // Mixed indentation
 5158            \t \t  \t   \tabc   // Mixed indentation
 5159            «\t\t\t
 5160            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5161        "}
 5162        .replace("·", "")
 5163        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5164    );
 5165
 5166    // SINGLE SELECTION
 5167    // Ln.1 "«" tests empty lines
 5168    // Ln.11 tests just leading whitespace
 5169    cx.set_state(indoc! {"
 5170        «
 5171        abc                 // No indentation
 5172         abc                // 1 space (< 3 so dont convert)
 5173          abc               // 2 spaces (< 3 so dont convert)
 5174           abc              // 3 spaces (convert)
 5175             abc            // 5 spaces (1 tab + 2 spaces)
 5176        \t\t\tabc           // Already tab indented
 5177        \t abc              // Tab followed by space
 5178         \tabc              // Space followed by tab (should be consumed due to tab)
 5179        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5180           \t  \t
 5181           abc   \t         // Only the leading spaces should be convertedˇ»
 5182    "});
 5183    cx.update_editor(|e, window, cx| {
 5184        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5185    });
 5186    cx.assert_editor_state(indoc! {"
 5187        «
 5188        abc                 // No indentation
 5189         abc                // 1 space (< 3 so dont convert)
 5190          abc               // 2 spaces (< 3 so dont convert)
 5191        \tabc              // 3 spaces (convert)
 5192        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5193        \t\t\tabc           // Already tab indented
 5194        \t abc              // Tab followed by space
 5195        \tabc              // Space followed by tab (should be consumed due to tab)
 5196        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5197        \t\t\t
 5198        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5199    "});
 5200}
 5201
 5202#[gpui::test]
 5203async fn test_toggle_case(cx: &mut TestAppContext) {
 5204    init_test(cx, |_| {});
 5205
 5206    let mut cx = EditorTestContext::new(cx).await;
 5207
 5208    // If all lower case -> upper case
 5209    cx.set_state(indoc! {"
 5210        «hello worldˇ»
 5211    "});
 5212    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5213    cx.assert_editor_state(indoc! {"
 5214        «HELLO WORLDˇ»
 5215    "});
 5216
 5217    // If all upper case -> lower case
 5218    cx.set_state(indoc! {"
 5219        «HELLO WORLDˇ»
 5220    "});
 5221    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5222    cx.assert_editor_state(indoc! {"
 5223        «hello worldˇ»
 5224    "});
 5225
 5226    // If any upper case characters are identified -> lower case
 5227    // This matches JetBrains IDEs
 5228    cx.set_state(indoc! {"
 5229        «hEllo worldˇ»
 5230    "});
 5231    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5232    cx.assert_editor_state(indoc! {"
 5233        «hello worldˇ»
 5234    "});
 5235}
 5236
 5237#[gpui::test]
 5238async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5239    init_test(cx, |_| {});
 5240
 5241    let mut cx = EditorTestContext::new(cx).await;
 5242
 5243    cx.set_state(indoc! {"
 5244        «implement-windows-supportˇ»
 5245    "});
 5246    cx.update_editor(|e, window, cx| {
 5247        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5248    });
 5249    cx.assert_editor_state(indoc! {"
 5250        «Implement windows supportˇ»
 5251    "});
 5252}
 5253
 5254#[gpui::test]
 5255async fn test_manipulate_text(cx: &mut TestAppContext) {
 5256    init_test(cx, |_| {});
 5257
 5258    let mut cx = EditorTestContext::new(cx).await;
 5259
 5260    // Test convert_to_upper_case()
 5261    cx.set_state(indoc! {"
 5262        «hello worldˇ»
 5263    "});
 5264    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5265    cx.assert_editor_state(indoc! {"
 5266        «HELLO WORLDˇ»
 5267    "});
 5268
 5269    // Test convert_to_lower_case()
 5270    cx.set_state(indoc! {"
 5271        «HELLO WORLDˇ»
 5272    "});
 5273    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5274    cx.assert_editor_state(indoc! {"
 5275        «hello worldˇ»
 5276    "});
 5277
 5278    // Test multiple line, single selection case
 5279    cx.set_state(indoc! {"
 5280        «The quick brown
 5281        fox jumps over
 5282        the lazy dogˇ»
 5283    "});
 5284    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5285    cx.assert_editor_state(indoc! {"
 5286        «The Quick Brown
 5287        Fox Jumps Over
 5288        The Lazy Dogˇ»
 5289    "});
 5290
 5291    // Test multiple line, single selection case
 5292    cx.set_state(indoc! {"
 5293        «The quick brown
 5294        fox jumps over
 5295        the lazy dogˇ»
 5296    "});
 5297    cx.update_editor(|e, window, cx| {
 5298        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5299    });
 5300    cx.assert_editor_state(indoc! {"
 5301        «TheQuickBrown
 5302        FoxJumpsOver
 5303        TheLazyDogˇ»
 5304    "});
 5305
 5306    // From here on out, test more complex cases of manipulate_text()
 5307
 5308    // Test no selection case - should affect words cursors are in
 5309    // Cursor at beginning, middle, and end of word
 5310    cx.set_state(indoc! {"
 5311        ˇhello big beauˇtiful worldˇ
 5312    "});
 5313    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5314    cx.assert_editor_state(indoc! {"
 5315        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5316    "});
 5317
 5318    // Test multiple selections on a single line and across multiple lines
 5319    cx.set_state(indoc! {"
 5320        «Theˇ» quick «brown
 5321        foxˇ» jumps «overˇ»
 5322        the «lazyˇ» dog
 5323    "});
 5324    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5325    cx.assert_editor_state(indoc! {"
 5326        «THEˇ» quick «BROWN
 5327        FOXˇ» jumps «OVERˇ»
 5328        the «LAZYˇ» dog
 5329    "});
 5330
 5331    // Test case where text length grows
 5332    cx.set_state(indoc! {"
 5333        «tschüߡ»
 5334    "});
 5335    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5336    cx.assert_editor_state(indoc! {"
 5337        «TSCHÜSSˇ»
 5338    "});
 5339
 5340    // Test to make sure we don't crash when text shrinks
 5341    cx.set_state(indoc! {"
 5342        aaa_bbbˇ
 5343    "});
 5344    cx.update_editor(|e, window, cx| {
 5345        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5346    });
 5347    cx.assert_editor_state(indoc! {"
 5348        «aaaBbbˇ»
 5349    "});
 5350
 5351    // Test to make sure we all aware of the fact that each word can grow and shrink
 5352    // Final selections should be aware of this fact
 5353    cx.set_state(indoc! {"
 5354        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5355    "});
 5356    cx.update_editor(|e, window, cx| {
 5357        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5358    });
 5359    cx.assert_editor_state(indoc! {"
 5360        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5361    "});
 5362
 5363    cx.set_state(indoc! {"
 5364        «hElLo, WoRld!ˇ»
 5365    "});
 5366    cx.update_editor(|e, window, cx| {
 5367        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5368    });
 5369    cx.assert_editor_state(indoc! {"
 5370        «HeLlO, wOrLD!ˇ»
 5371    "});
 5372
 5373    // Test selections with `line_mode() = true`.
 5374    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 5375    cx.set_state(indoc! {"
 5376        «The quick brown
 5377        fox jumps over
 5378        tˇ»he lazy dog
 5379    "});
 5380    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5381    cx.assert_editor_state(indoc! {"
 5382        «THE QUICK BROWN
 5383        FOX JUMPS OVER
 5384        THE LAZY DOGˇ»
 5385    "});
 5386}
 5387
 5388#[gpui::test]
 5389fn test_duplicate_line(cx: &mut TestAppContext) {
 5390    init_test(cx, |_| {});
 5391
 5392    let editor = cx.add_window(|window, cx| {
 5393        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5394        build_editor(buffer, window, cx)
 5395    });
 5396    _ = editor.update(cx, |editor, window, cx| {
 5397        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5398            s.select_display_ranges([
 5399                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5400                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5401                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5402                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5403            ])
 5404        });
 5405        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5406        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5407        assert_eq!(
 5408            editor.selections.display_ranges(cx),
 5409            vec![
 5410                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5411                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5412                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5413                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5414            ]
 5415        );
 5416    });
 5417
 5418    let editor = cx.add_window(|window, cx| {
 5419        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5420        build_editor(buffer, window, cx)
 5421    });
 5422    _ = editor.update(cx, |editor, window, cx| {
 5423        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5424            s.select_display_ranges([
 5425                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5426                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5427            ])
 5428        });
 5429        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5430        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5431        assert_eq!(
 5432            editor.selections.display_ranges(cx),
 5433            vec![
 5434                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5435                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5436            ]
 5437        );
 5438    });
 5439
 5440    // With `move_upwards` the selections stay in place, except for
 5441    // the lines inserted above them
 5442    let editor = cx.add_window(|window, cx| {
 5443        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5444        build_editor(buffer, window, cx)
 5445    });
 5446    _ = editor.update(cx, |editor, window, cx| {
 5447        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5448            s.select_display_ranges([
 5449                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5450                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5451                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5452                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5453            ])
 5454        });
 5455        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5456        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5457        assert_eq!(
 5458            editor.selections.display_ranges(cx),
 5459            vec![
 5460                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5461                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5462                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5463                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5464            ]
 5465        );
 5466    });
 5467
 5468    let editor = cx.add_window(|window, cx| {
 5469        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5470        build_editor(buffer, window, cx)
 5471    });
 5472    _ = editor.update(cx, |editor, window, cx| {
 5473        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5474            s.select_display_ranges([
 5475                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5476                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5477            ])
 5478        });
 5479        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5480        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5481        assert_eq!(
 5482            editor.selections.display_ranges(cx),
 5483            vec![
 5484                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5485                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5486            ]
 5487        );
 5488    });
 5489
 5490    let editor = cx.add_window(|window, cx| {
 5491        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5492        build_editor(buffer, window, cx)
 5493    });
 5494    _ = editor.update(cx, |editor, window, cx| {
 5495        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5496            s.select_display_ranges([
 5497                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5498                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5499            ])
 5500        });
 5501        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5502        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5503        assert_eq!(
 5504            editor.selections.display_ranges(cx),
 5505            vec![
 5506                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5507                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5508            ]
 5509        );
 5510    });
 5511}
 5512
 5513#[gpui::test]
 5514fn test_move_line_up_down(cx: &mut TestAppContext) {
 5515    init_test(cx, |_| {});
 5516
 5517    let editor = cx.add_window(|window, cx| {
 5518        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5519        build_editor(buffer, window, cx)
 5520    });
 5521    _ = editor.update(cx, |editor, window, cx| {
 5522        editor.fold_creases(
 5523            vec![
 5524                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5525                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5526                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5527            ],
 5528            true,
 5529            window,
 5530            cx,
 5531        );
 5532        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5533            s.select_display_ranges([
 5534                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5535                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5536                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5537                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5538            ])
 5539        });
 5540        assert_eq!(
 5541            editor.display_text(cx),
 5542            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5543        );
 5544
 5545        editor.move_line_up(&MoveLineUp, window, cx);
 5546        assert_eq!(
 5547            editor.display_text(cx),
 5548            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5549        );
 5550        assert_eq!(
 5551            editor.selections.display_ranges(cx),
 5552            vec![
 5553                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5554                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5555                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5556                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5557            ]
 5558        );
 5559    });
 5560
 5561    _ = editor.update(cx, |editor, window, cx| {
 5562        editor.move_line_down(&MoveLineDown, window, cx);
 5563        assert_eq!(
 5564            editor.display_text(cx),
 5565            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5566        );
 5567        assert_eq!(
 5568            editor.selections.display_ranges(cx),
 5569            vec![
 5570                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5571                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5572                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5573                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5574            ]
 5575        );
 5576    });
 5577
 5578    _ = editor.update(cx, |editor, window, cx| {
 5579        editor.move_line_down(&MoveLineDown, window, cx);
 5580        assert_eq!(
 5581            editor.display_text(cx),
 5582            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5583        );
 5584        assert_eq!(
 5585            editor.selections.display_ranges(cx),
 5586            vec![
 5587                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5588                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5589                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5590                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5591            ]
 5592        );
 5593    });
 5594
 5595    _ = editor.update(cx, |editor, window, cx| {
 5596        editor.move_line_up(&MoveLineUp, window, cx);
 5597        assert_eq!(
 5598            editor.display_text(cx),
 5599            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5600        );
 5601        assert_eq!(
 5602            editor.selections.display_ranges(cx),
 5603            vec![
 5604                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5605                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5606                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5607                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5608            ]
 5609        );
 5610    });
 5611}
 5612
 5613#[gpui::test]
 5614fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5615    init_test(cx, |_| {});
 5616    let editor = cx.add_window(|window, cx| {
 5617        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5618        build_editor(buffer, window, cx)
 5619    });
 5620    _ = editor.update(cx, |editor, window, cx| {
 5621        editor.fold_creases(
 5622            vec![Crease::simple(
 5623                Point::new(6, 4)..Point::new(7, 4),
 5624                FoldPlaceholder::test(),
 5625            )],
 5626            true,
 5627            window,
 5628            cx,
 5629        );
 5630        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5631            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5632        });
 5633        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5634        editor.move_line_up(&MoveLineUp, window, cx);
 5635        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5636        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5637    });
 5638}
 5639
 5640#[gpui::test]
 5641fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5642    init_test(cx, |_| {});
 5643
 5644    let editor = cx.add_window(|window, cx| {
 5645        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5646        build_editor(buffer, window, cx)
 5647    });
 5648    _ = editor.update(cx, |editor, window, cx| {
 5649        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5650        editor.insert_blocks(
 5651            [BlockProperties {
 5652                style: BlockStyle::Fixed,
 5653                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5654                height: Some(1),
 5655                render: Arc::new(|_| div().into_any()),
 5656                priority: 0,
 5657            }],
 5658            Some(Autoscroll::fit()),
 5659            cx,
 5660        );
 5661        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5662            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5663        });
 5664        editor.move_line_down(&MoveLineDown, window, cx);
 5665    });
 5666}
 5667
 5668#[gpui::test]
 5669async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5670    init_test(cx, |_| {});
 5671
 5672    let mut cx = EditorTestContext::new(cx).await;
 5673    cx.set_state(
 5674        &"
 5675            ˇzero
 5676            one
 5677            two
 5678            three
 5679            four
 5680            five
 5681        "
 5682        .unindent(),
 5683    );
 5684
 5685    // Create a four-line block that replaces three lines of text.
 5686    cx.update_editor(|editor, window, cx| {
 5687        let snapshot = editor.snapshot(window, cx);
 5688        let snapshot = &snapshot.buffer_snapshot;
 5689        let placement = BlockPlacement::Replace(
 5690            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5691        );
 5692        editor.insert_blocks(
 5693            [BlockProperties {
 5694                placement,
 5695                height: Some(4),
 5696                style: BlockStyle::Sticky,
 5697                render: Arc::new(|_| gpui::div().into_any_element()),
 5698                priority: 0,
 5699            }],
 5700            None,
 5701            cx,
 5702        );
 5703    });
 5704
 5705    // Move down so that the cursor touches the block.
 5706    cx.update_editor(|editor, window, cx| {
 5707        editor.move_down(&Default::default(), window, cx);
 5708    });
 5709    cx.assert_editor_state(
 5710        &"
 5711            zero
 5712            «one
 5713            two
 5714            threeˇ»
 5715            four
 5716            five
 5717        "
 5718        .unindent(),
 5719    );
 5720
 5721    // Move down past the block.
 5722    cx.update_editor(|editor, window, cx| {
 5723        editor.move_down(&Default::default(), window, cx);
 5724    });
 5725    cx.assert_editor_state(
 5726        &"
 5727            zero
 5728            one
 5729            two
 5730            three
 5731            ˇfour
 5732            five
 5733        "
 5734        .unindent(),
 5735    );
 5736}
 5737
 5738#[gpui::test]
 5739fn test_transpose(cx: &mut TestAppContext) {
 5740    init_test(cx, |_| {});
 5741
 5742    _ = cx.add_window(|window, cx| {
 5743        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5744        editor.set_style(EditorStyle::default(), window, cx);
 5745        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5746            s.select_ranges([1..1])
 5747        });
 5748        editor.transpose(&Default::default(), window, cx);
 5749        assert_eq!(editor.text(cx), "bac");
 5750        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5751
 5752        editor.transpose(&Default::default(), window, cx);
 5753        assert_eq!(editor.text(cx), "bca");
 5754        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5755
 5756        editor.transpose(&Default::default(), window, cx);
 5757        assert_eq!(editor.text(cx), "bac");
 5758        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5759
 5760        editor
 5761    });
 5762
 5763    _ = cx.add_window(|window, cx| {
 5764        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5765        editor.set_style(EditorStyle::default(), window, cx);
 5766        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5767            s.select_ranges([3..3])
 5768        });
 5769        editor.transpose(&Default::default(), window, cx);
 5770        assert_eq!(editor.text(cx), "acb\nde");
 5771        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5772
 5773        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5774            s.select_ranges([4..4])
 5775        });
 5776        editor.transpose(&Default::default(), window, cx);
 5777        assert_eq!(editor.text(cx), "acbd\ne");
 5778        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5779
 5780        editor.transpose(&Default::default(), window, cx);
 5781        assert_eq!(editor.text(cx), "acbde\n");
 5782        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5783
 5784        editor.transpose(&Default::default(), window, cx);
 5785        assert_eq!(editor.text(cx), "acbd\ne");
 5786        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5787
 5788        editor
 5789    });
 5790
 5791    _ = cx.add_window(|window, cx| {
 5792        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5793        editor.set_style(EditorStyle::default(), window, cx);
 5794        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5795            s.select_ranges([1..1, 2..2, 4..4])
 5796        });
 5797        editor.transpose(&Default::default(), window, cx);
 5798        assert_eq!(editor.text(cx), "bacd\ne");
 5799        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5800
 5801        editor.transpose(&Default::default(), window, cx);
 5802        assert_eq!(editor.text(cx), "bcade\n");
 5803        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5804
 5805        editor.transpose(&Default::default(), window, cx);
 5806        assert_eq!(editor.text(cx), "bcda\ne");
 5807        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5808
 5809        editor.transpose(&Default::default(), window, cx);
 5810        assert_eq!(editor.text(cx), "bcade\n");
 5811        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5812
 5813        editor.transpose(&Default::default(), window, cx);
 5814        assert_eq!(editor.text(cx), "bcaed\n");
 5815        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5816
 5817        editor
 5818    });
 5819
 5820    _ = cx.add_window(|window, cx| {
 5821        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5822        editor.set_style(EditorStyle::default(), window, cx);
 5823        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5824            s.select_ranges([4..4])
 5825        });
 5826        editor.transpose(&Default::default(), window, cx);
 5827        assert_eq!(editor.text(cx), "🏀🍐✋");
 5828        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5829
 5830        editor.transpose(&Default::default(), window, cx);
 5831        assert_eq!(editor.text(cx), "🏀✋🍐");
 5832        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5833
 5834        editor.transpose(&Default::default(), window, cx);
 5835        assert_eq!(editor.text(cx), "🏀🍐✋");
 5836        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5837
 5838        editor
 5839    });
 5840}
 5841
 5842#[gpui::test]
 5843async fn test_rewrap(cx: &mut TestAppContext) {
 5844    init_test(cx, |settings| {
 5845        settings.languages.0.extend([
 5846            (
 5847                "Markdown".into(),
 5848                LanguageSettingsContent {
 5849                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5850                    preferred_line_length: Some(40),
 5851                    ..Default::default()
 5852                },
 5853            ),
 5854            (
 5855                "Plain Text".into(),
 5856                LanguageSettingsContent {
 5857                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5858                    preferred_line_length: Some(40),
 5859                    ..Default::default()
 5860                },
 5861            ),
 5862            (
 5863                "C++".into(),
 5864                LanguageSettingsContent {
 5865                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5866                    preferred_line_length: Some(40),
 5867                    ..Default::default()
 5868                },
 5869            ),
 5870            (
 5871                "Python".into(),
 5872                LanguageSettingsContent {
 5873                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5874                    preferred_line_length: Some(40),
 5875                    ..Default::default()
 5876                },
 5877            ),
 5878            (
 5879                "Rust".into(),
 5880                LanguageSettingsContent {
 5881                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5882                    preferred_line_length: Some(40),
 5883                    ..Default::default()
 5884                },
 5885            ),
 5886        ])
 5887    });
 5888
 5889    let mut cx = EditorTestContext::new(cx).await;
 5890
 5891    let cpp_language = Arc::new(Language::new(
 5892        LanguageConfig {
 5893            name: "C++".into(),
 5894            line_comments: vec!["// ".into()],
 5895            ..LanguageConfig::default()
 5896        },
 5897        None,
 5898    ));
 5899    let python_language = Arc::new(Language::new(
 5900        LanguageConfig {
 5901            name: "Python".into(),
 5902            line_comments: vec!["# ".into()],
 5903            ..LanguageConfig::default()
 5904        },
 5905        None,
 5906    ));
 5907    let markdown_language = Arc::new(Language::new(
 5908        LanguageConfig {
 5909            name: "Markdown".into(),
 5910            rewrap_prefixes: vec![
 5911                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 5912                regex::Regex::new("[-*+]\\s+").unwrap(),
 5913            ],
 5914            ..LanguageConfig::default()
 5915        },
 5916        None,
 5917    ));
 5918    let rust_language = Arc::new(
 5919        Language::new(
 5920            LanguageConfig {
 5921                name: "Rust".into(),
 5922                line_comments: vec!["// ".into(), "/// ".into()],
 5923                ..LanguageConfig::default()
 5924            },
 5925            Some(tree_sitter_rust::LANGUAGE.into()),
 5926        )
 5927        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 5928        .unwrap(),
 5929    );
 5930
 5931    let plaintext_language = Arc::new(Language::new(
 5932        LanguageConfig {
 5933            name: "Plain Text".into(),
 5934            ..LanguageConfig::default()
 5935        },
 5936        None,
 5937    ));
 5938
 5939    // Test basic rewrapping of a long line with a cursor
 5940    assert_rewrap(
 5941        indoc! {"
 5942            // ˇThis is a long comment that needs to be wrapped.
 5943        "},
 5944        indoc! {"
 5945            // ˇThis is a long comment that needs to
 5946            // be wrapped.
 5947        "},
 5948        cpp_language.clone(),
 5949        &mut cx,
 5950    );
 5951
 5952    // Test rewrapping a full selection
 5953    assert_rewrap(
 5954        indoc! {"
 5955            «// This selected long comment needs to be wrapped.ˇ»"
 5956        },
 5957        indoc! {"
 5958            «// This selected long comment needs to
 5959            // be wrapped.ˇ»"
 5960        },
 5961        cpp_language.clone(),
 5962        &mut cx,
 5963    );
 5964
 5965    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 5966    assert_rewrap(
 5967        indoc! {"
 5968            // ˇThis is the first line.
 5969            // Thisˇ is the second line.
 5970            // This is the thirdˇ line, all part of one paragraph.
 5971         "},
 5972        indoc! {"
 5973            // ˇThis is the first line. Thisˇ is the
 5974            // second line. This is the thirdˇ line,
 5975            // all part of one paragraph.
 5976         "},
 5977        cpp_language.clone(),
 5978        &mut cx,
 5979    );
 5980
 5981    // Test multiple cursors in different paragraphs trigger separate rewraps
 5982    assert_rewrap(
 5983        indoc! {"
 5984            // ˇThis is the first paragraph, first line.
 5985            // ˇThis is the first paragraph, second line.
 5986
 5987            // ˇThis is the second paragraph, first line.
 5988            // ˇThis is the second paragraph, second line.
 5989        "},
 5990        indoc! {"
 5991            // ˇThis is the first paragraph, first
 5992            // line. ˇThis is the first paragraph,
 5993            // second line.
 5994
 5995            // ˇThis is the second paragraph, first
 5996            // line. ˇThis is the second paragraph,
 5997            // second line.
 5998        "},
 5999        cpp_language.clone(),
 6000        &mut cx,
 6001    );
 6002
 6003    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 6004    assert_rewrap(
 6005        indoc! {"
 6006            «// A regular long long comment to be wrapped.
 6007            /// A documentation long comment to be wrapped.ˇ»
 6008          "},
 6009        indoc! {"
 6010            «// A regular long long comment to be
 6011            // wrapped.
 6012            /// A documentation long comment to be
 6013            /// wrapped.ˇ»
 6014          "},
 6015        rust_language.clone(),
 6016        &mut cx,
 6017    );
 6018
 6019    // Test that change in indentation level trigger seperate rewraps
 6020    assert_rewrap(
 6021        indoc! {"
 6022            fn foo() {
 6023                «// This is a long comment at the base indent.
 6024                    // This is a long comment at the next indent.ˇ»
 6025            }
 6026        "},
 6027        indoc! {"
 6028            fn foo() {
 6029                «// This is a long comment at the
 6030                // base indent.
 6031                    // This is a long comment at the
 6032                    // next indent.ˇ»
 6033            }
 6034        "},
 6035        rust_language.clone(),
 6036        &mut cx,
 6037    );
 6038
 6039    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6040    assert_rewrap(
 6041        indoc! {"
 6042            # ˇThis is a long comment using a pound sign.
 6043        "},
 6044        indoc! {"
 6045            # ˇThis is a long comment using a pound
 6046            # sign.
 6047        "},
 6048        python_language,
 6049        &mut cx,
 6050    );
 6051
 6052    // Test rewrapping only affects comments, not code even when selected
 6053    assert_rewrap(
 6054        indoc! {"
 6055            «/// This doc comment is long and should be wrapped.
 6056            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6057        "},
 6058        indoc! {"
 6059            «/// This doc comment is long and should
 6060            /// be wrapped.
 6061            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6062        "},
 6063        rust_language.clone(),
 6064        &mut cx,
 6065    );
 6066
 6067    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6068    assert_rewrap(
 6069        indoc! {"
 6070            # Header
 6071
 6072            A long long long line of markdown text to wrap.ˇ
 6073         "},
 6074        indoc! {"
 6075            # Header
 6076
 6077            A long long long line of markdown text
 6078            to wrap.ˇ
 6079         "},
 6080        markdown_language.clone(),
 6081        &mut cx,
 6082    );
 6083
 6084    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6085    assert_rewrap(
 6086        indoc! {"
 6087            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6088            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6089            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6090        "},
 6091        indoc! {"
 6092            «1. This is a numbered list item that is
 6093               very long and needs to be wrapped
 6094               properly.
 6095            2. This is a numbered list item that is
 6096               very long and needs to be wrapped
 6097               properly.
 6098            - This is an unordered list item that is
 6099              also very long and should not merge
 6100              with the numbered item.ˇ»
 6101        "},
 6102        markdown_language.clone(),
 6103        &mut cx,
 6104    );
 6105
 6106    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6107    assert_rewrap(
 6108        indoc! {"
 6109            «1. This is a numbered list item that is
 6110            very long and needs to be wrapped
 6111            properly.
 6112            2. This is a numbered list item that is
 6113            very long and needs to be wrapped
 6114            properly.
 6115            - This is an unordered list item that is
 6116            also very long and should not merge with
 6117            the numbered item.ˇ»
 6118        "},
 6119        indoc! {"
 6120            «1. This is a numbered list item that is
 6121               very long and needs to be wrapped
 6122               properly.
 6123            2. This is a numbered list item that is
 6124               very long and needs to be wrapped
 6125               properly.
 6126            - This is an unordered list item that is
 6127              also very long and should not merge
 6128              with the numbered item.ˇ»
 6129        "},
 6130        markdown_language.clone(),
 6131        &mut cx,
 6132    );
 6133
 6134    // Test that rewrapping maintain indents even when they already exists.
 6135    assert_rewrap(
 6136        indoc! {"
 6137            «1. This is a numbered list
 6138               item that is very long and needs to be wrapped properly.
 6139            2. This is a numbered list
 6140               item that is very long and needs to be wrapped properly.
 6141            - This is an unordered list item that is also very long and
 6142              should not merge with the numbered item.ˇ»
 6143        "},
 6144        indoc! {"
 6145            «1. This is a numbered list item that is
 6146               very long and needs to be wrapped
 6147               properly.
 6148            2. This is a numbered list item that is
 6149               very long and needs to be wrapped
 6150               properly.
 6151            - This is an unordered list item that is
 6152              also very long and should not merge
 6153              with the numbered item.ˇ»
 6154        "},
 6155        markdown_language,
 6156        &mut cx,
 6157    );
 6158
 6159    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6160    assert_rewrap(
 6161        indoc! {"
 6162            ˇThis is a very long line of plain text that will be wrapped.
 6163        "},
 6164        indoc! {"
 6165            ˇThis is a very long line of plain text
 6166            that will be wrapped.
 6167        "},
 6168        plaintext_language.clone(),
 6169        &mut cx,
 6170    );
 6171
 6172    // Test that non-commented code acts as a paragraph boundary within a selection
 6173    assert_rewrap(
 6174        indoc! {"
 6175               «// This is the first long comment block to be wrapped.
 6176               fn my_func(a: u32);
 6177               // This is the second long comment block to be wrapped.ˇ»
 6178           "},
 6179        indoc! {"
 6180               «// This is the first long comment block
 6181               // to be wrapped.
 6182               fn my_func(a: u32);
 6183               // This is the second long comment block
 6184               // to be wrapped.ˇ»
 6185           "},
 6186        rust_language,
 6187        &mut cx,
 6188    );
 6189
 6190    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6191    assert_rewrap(
 6192        indoc! {"
 6193            «ˇThis is a very long line that will be wrapped.
 6194
 6195            This is another paragraph in the same selection.»
 6196
 6197            «\tThis is a very long indented line that will be wrapped.ˇ»
 6198         "},
 6199        indoc! {"
 6200            «ˇThis is a very long line that will be
 6201            wrapped.
 6202
 6203            This is another paragraph in the same
 6204            selection.»
 6205
 6206            «\tThis is a very long indented line
 6207            \tthat will be wrapped.ˇ»
 6208         "},
 6209        plaintext_language,
 6210        &mut cx,
 6211    );
 6212
 6213    // Test that an empty comment line acts as a paragraph boundary
 6214    assert_rewrap(
 6215        indoc! {"
 6216            // ˇThis is a long comment that will be wrapped.
 6217            //
 6218            // And this is another long comment that will also be wrapped.ˇ
 6219         "},
 6220        indoc! {"
 6221            // ˇThis is a long comment that will be
 6222            // wrapped.
 6223            //
 6224            // And this is another long comment that
 6225            // will also be wrapped.ˇ
 6226         "},
 6227        cpp_language,
 6228        &mut cx,
 6229    );
 6230
 6231    #[track_caller]
 6232    fn assert_rewrap(
 6233        unwrapped_text: &str,
 6234        wrapped_text: &str,
 6235        language: Arc<Language>,
 6236        cx: &mut EditorTestContext,
 6237    ) {
 6238        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6239        cx.set_state(unwrapped_text);
 6240        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6241        cx.assert_editor_state(wrapped_text);
 6242    }
 6243}
 6244
 6245#[gpui::test]
 6246async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6247    init_test(cx, |settings| {
 6248        settings.languages.0.extend([(
 6249            "Rust".into(),
 6250            LanguageSettingsContent {
 6251                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6252                preferred_line_length: Some(40),
 6253                ..Default::default()
 6254            },
 6255        )])
 6256    });
 6257
 6258    let mut cx = EditorTestContext::new(cx).await;
 6259
 6260    let rust_lang = Arc::new(
 6261        Language::new(
 6262            LanguageConfig {
 6263                name: "Rust".into(),
 6264                line_comments: vec!["// ".into()],
 6265                block_comment: Some(BlockCommentConfig {
 6266                    start: "/*".into(),
 6267                    end: "*/".into(),
 6268                    prefix: "* ".into(),
 6269                    tab_size: 1,
 6270                }),
 6271                documentation_comment: Some(BlockCommentConfig {
 6272                    start: "/**".into(),
 6273                    end: "*/".into(),
 6274                    prefix: "* ".into(),
 6275                    tab_size: 1,
 6276                }),
 6277
 6278                ..LanguageConfig::default()
 6279            },
 6280            Some(tree_sitter_rust::LANGUAGE.into()),
 6281        )
 6282        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6283        .unwrap(),
 6284    );
 6285
 6286    // regular block comment
 6287    assert_rewrap(
 6288        indoc! {"
 6289            /*
 6290             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6291             */
 6292            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6293        "},
 6294        indoc! {"
 6295            /*
 6296             *ˇ Lorem ipsum dolor sit amet,
 6297             * consectetur adipiscing elit.
 6298             */
 6299            /*
 6300             *ˇ Lorem ipsum dolor sit amet,
 6301             * consectetur adipiscing elit.
 6302             */
 6303        "},
 6304        rust_lang.clone(),
 6305        &mut cx,
 6306    );
 6307
 6308    // indent is respected
 6309    assert_rewrap(
 6310        indoc! {"
 6311            {}
 6312                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6313        "},
 6314        indoc! {"
 6315            {}
 6316                /*
 6317                 *ˇ Lorem ipsum dolor sit amet,
 6318                 * consectetur adipiscing elit.
 6319                 */
 6320        "},
 6321        rust_lang.clone(),
 6322        &mut cx,
 6323    );
 6324
 6325    // short block comments with inline delimiters
 6326    assert_rewrap(
 6327        indoc! {"
 6328            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6329            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6330             */
 6331            /*
 6332             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6333        "},
 6334        indoc! {"
 6335            /*
 6336             *ˇ Lorem ipsum dolor sit amet,
 6337             * consectetur adipiscing elit.
 6338             */
 6339            /*
 6340             *ˇ Lorem ipsum dolor sit amet,
 6341             * consectetur adipiscing elit.
 6342             */
 6343            /*
 6344             *ˇ Lorem ipsum dolor sit amet,
 6345             * consectetur adipiscing elit.
 6346             */
 6347        "},
 6348        rust_lang.clone(),
 6349        &mut cx,
 6350    );
 6351
 6352    // multiline block comment with inline start/end delimiters
 6353    assert_rewrap(
 6354        indoc! {"
 6355            /*ˇ Lorem ipsum dolor sit amet,
 6356             * consectetur adipiscing elit. */
 6357        "},
 6358        indoc! {"
 6359            /*
 6360             *ˇ Lorem ipsum dolor sit amet,
 6361             * consectetur adipiscing elit.
 6362             */
 6363        "},
 6364        rust_lang.clone(),
 6365        &mut cx,
 6366    );
 6367
 6368    // block comment rewrap still respects paragraph bounds
 6369    assert_rewrap(
 6370        indoc! {"
 6371            /*
 6372             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6373             *
 6374             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6375             */
 6376        "},
 6377        indoc! {"
 6378            /*
 6379             *ˇ Lorem ipsum dolor sit amet,
 6380             * consectetur adipiscing elit.
 6381             *
 6382             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6383             */
 6384        "},
 6385        rust_lang.clone(),
 6386        &mut cx,
 6387    );
 6388
 6389    // documentation comments
 6390    assert_rewrap(
 6391        indoc! {"
 6392            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6393            /**
 6394             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6395             */
 6396        "},
 6397        indoc! {"
 6398            /**
 6399             *ˇ Lorem ipsum dolor sit amet,
 6400             * consectetur adipiscing elit.
 6401             */
 6402            /**
 6403             *ˇ Lorem ipsum dolor sit amet,
 6404             * consectetur adipiscing elit.
 6405             */
 6406        "},
 6407        rust_lang.clone(),
 6408        &mut cx,
 6409    );
 6410
 6411    // different, adjacent comments
 6412    assert_rewrap(
 6413        indoc! {"
 6414            /**
 6415             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6416             */
 6417            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6418            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6419        "},
 6420        indoc! {"
 6421            /**
 6422             *ˇ Lorem ipsum dolor sit amet,
 6423             * consectetur adipiscing elit.
 6424             */
 6425            /*
 6426             *ˇ Lorem ipsum dolor sit amet,
 6427             * consectetur adipiscing elit.
 6428             */
 6429            //ˇ Lorem ipsum dolor sit amet,
 6430            // consectetur adipiscing elit.
 6431        "},
 6432        rust_lang.clone(),
 6433        &mut cx,
 6434    );
 6435
 6436    // selection w/ single short block comment
 6437    assert_rewrap(
 6438        indoc! {"
 6439            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6440        "},
 6441        indoc! {"
 6442            «/*
 6443             * Lorem ipsum dolor sit amet,
 6444             * consectetur adipiscing elit.
 6445             */ˇ»
 6446        "},
 6447        rust_lang.clone(),
 6448        &mut cx,
 6449    );
 6450
 6451    // rewrapping a single comment w/ abutting comments
 6452    assert_rewrap(
 6453        indoc! {"
 6454            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6455            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6456        "},
 6457        indoc! {"
 6458            /*
 6459             * ˇLorem ipsum dolor sit amet,
 6460             * consectetur adipiscing elit.
 6461             */
 6462            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6463        "},
 6464        rust_lang.clone(),
 6465        &mut cx,
 6466    );
 6467
 6468    // selection w/ non-abutting short block comments
 6469    assert_rewrap(
 6470        indoc! {"
 6471            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6472
 6473            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6474        "},
 6475        indoc! {"
 6476            «/*
 6477             * Lorem ipsum dolor sit amet,
 6478             * consectetur adipiscing elit.
 6479             */
 6480
 6481            /*
 6482             * Lorem ipsum dolor sit amet,
 6483             * consectetur adipiscing elit.
 6484             */ˇ»
 6485        "},
 6486        rust_lang.clone(),
 6487        &mut cx,
 6488    );
 6489
 6490    // selection of multiline block comments
 6491    assert_rewrap(
 6492        indoc! {"
 6493            «/* Lorem ipsum dolor sit amet,
 6494             * consectetur adipiscing elit. */ˇ»
 6495        "},
 6496        indoc! {"
 6497            «/*
 6498             * Lorem ipsum dolor sit amet,
 6499             * consectetur adipiscing elit.
 6500             */ˇ»
 6501        "},
 6502        rust_lang.clone(),
 6503        &mut cx,
 6504    );
 6505
 6506    // partial selection of multiline block comments
 6507    assert_rewrap(
 6508        indoc! {"
 6509            «/* Lorem ipsum dolor sit amet,ˇ»
 6510             * consectetur adipiscing elit. */
 6511            /* Lorem ipsum dolor sit amet,
 6512             «* consectetur adipiscing elit. */ˇ»
 6513        "},
 6514        indoc! {"
 6515            «/*
 6516             * Lorem ipsum dolor sit amet,ˇ»
 6517             * consectetur adipiscing elit. */
 6518            /* Lorem ipsum dolor sit amet,
 6519             «* consectetur adipiscing elit.
 6520             */ˇ»
 6521        "},
 6522        rust_lang.clone(),
 6523        &mut cx,
 6524    );
 6525
 6526    // selection w/ abutting short block comments
 6527    // TODO: should not be combined; should rewrap as 2 comments
 6528    assert_rewrap(
 6529        indoc! {"
 6530            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6531            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6532        "},
 6533        // desired behavior:
 6534        // indoc! {"
 6535        //     «/*
 6536        //      * Lorem ipsum dolor sit amet,
 6537        //      * consectetur adipiscing elit.
 6538        //      */
 6539        //     /*
 6540        //      * Lorem ipsum dolor sit amet,
 6541        //      * consectetur adipiscing elit.
 6542        //      */ˇ»
 6543        // "},
 6544        // actual behaviour:
 6545        indoc! {"
 6546            «/*
 6547             * Lorem ipsum dolor sit amet,
 6548             * consectetur adipiscing elit. Lorem
 6549             * ipsum dolor sit amet, consectetur
 6550             * adipiscing elit.
 6551             */ˇ»
 6552        "},
 6553        rust_lang.clone(),
 6554        &mut cx,
 6555    );
 6556
 6557    // TODO: same as above, but with delimiters on separate line
 6558    // assert_rewrap(
 6559    //     indoc! {"
 6560    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6561    //          */
 6562    //         /*
 6563    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6564    //     "},
 6565    //     // desired:
 6566    //     // indoc! {"
 6567    //     //     «/*
 6568    //     //      * Lorem ipsum dolor sit amet,
 6569    //     //      * consectetur adipiscing elit.
 6570    //     //      */
 6571    //     //     /*
 6572    //     //      * Lorem ipsum dolor sit amet,
 6573    //     //      * consectetur adipiscing elit.
 6574    //     //      */ˇ»
 6575    //     // "},
 6576    //     // actual: (but with trailing w/s on the empty lines)
 6577    //     indoc! {"
 6578    //         «/*
 6579    //          * Lorem ipsum dolor sit amet,
 6580    //          * consectetur adipiscing elit.
 6581    //          *
 6582    //          */
 6583    //         /*
 6584    //          *
 6585    //          * Lorem ipsum dolor sit amet,
 6586    //          * consectetur adipiscing elit.
 6587    //          */ˇ»
 6588    //     "},
 6589    //     rust_lang.clone(),
 6590    //     &mut cx,
 6591    // );
 6592
 6593    // TODO these are unhandled edge cases; not correct, just documenting known issues
 6594    assert_rewrap(
 6595        indoc! {"
 6596            /*
 6597             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6598             */
 6599            /*
 6600             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6601            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 6602        "},
 6603        // desired:
 6604        // indoc! {"
 6605        //     /*
 6606        //      *ˇ Lorem ipsum dolor sit amet,
 6607        //      * consectetur adipiscing elit.
 6608        //      */
 6609        //     /*
 6610        //      *ˇ Lorem ipsum dolor sit amet,
 6611        //      * consectetur adipiscing elit.
 6612        //      */
 6613        //     /*
 6614        //      *ˇ Lorem ipsum dolor sit amet
 6615        //      */ /* consectetur adipiscing elit. */
 6616        // "},
 6617        // actual:
 6618        indoc! {"
 6619            /*
 6620             //ˇ Lorem ipsum dolor sit amet,
 6621             // consectetur adipiscing elit.
 6622             */
 6623            /*
 6624             * //ˇ Lorem ipsum dolor sit amet,
 6625             * consectetur adipiscing elit.
 6626             */
 6627            /*
 6628             *ˇ Lorem ipsum dolor sit amet */ /*
 6629             * consectetur adipiscing elit.
 6630             */
 6631        "},
 6632        rust_lang,
 6633        &mut cx,
 6634    );
 6635
 6636    #[track_caller]
 6637    fn assert_rewrap(
 6638        unwrapped_text: &str,
 6639        wrapped_text: &str,
 6640        language: Arc<Language>,
 6641        cx: &mut EditorTestContext,
 6642    ) {
 6643        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6644        cx.set_state(unwrapped_text);
 6645        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6646        cx.assert_editor_state(wrapped_text);
 6647    }
 6648}
 6649
 6650#[gpui::test]
 6651async fn test_hard_wrap(cx: &mut TestAppContext) {
 6652    init_test(cx, |_| {});
 6653    let mut cx = EditorTestContext::new(cx).await;
 6654
 6655    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 6656    cx.update_editor(|editor, _, cx| {
 6657        editor.set_hard_wrap(Some(14), cx);
 6658    });
 6659
 6660    cx.set_state(indoc!(
 6661        "
 6662        one two three ˇ
 6663        "
 6664    ));
 6665    cx.simulate_input("four");
 6666    cx.run_until_parked();
 6667
 6668    cx.assert_editor_state(indoc!(
 6669        "
 6670        one two three
 6671        fourˇ
 6672        "
 6673    ));
 6674
 6675    cx.update_editor(|editor, window, cx| {
 6676        editor.newline(&Default::default(), window, cx);
 6677    });
 6678    cx.run_until_parked();
 6679    cx.assert_editor_state(indoc!(
 6680        "
 6681        one two three
 6682        four
 6683        ˇ
 6684        "
 6685    ));
 6686
 6687    cx.simulate_input("five");
 6688    cx.run_until_parked();
 6689    cx.assert_editor_state(indoc!(
 6690        "
 6691        one two three
 6692        four
 6693        fiveˇ
 6694        "
 6695    ));
 6696
 6697    cx.update_editor(|editor, window, cx| {
 6698        editor.newline(&Default::default(), window, cx);
 6699    });
 6700    cx.run_until_parked();
 6701    cx.simulate_input("# ");
 6702    cx.run_until_parked();
 6703    cx.assert_editor_state(indoc!(
 6704        "
 6705        one two three
 6706        four
 6707        five
 6708        # ˇ
 6709        "
 6710    ));
 6711
 6712    cx.update_editor(|editor, window, cx| {
 6713        editor.newline(&Default::default(), window, cx);
 6714    });
 6715    cx.run_until_parked();
 6716    cx.assert_editor_state(indoc!(
 6717        "
 6718        one two three
 6719        four
 6720        five
 6721        #\x20
 6722 6723        "
 6724    ));
 6725
 6726    cx.simulate_input(" 6");
 6727    cx.run_until_parked();
 6728    cx.assert_editor_state(indoc!(
 6729        "
 6730        one two three
 6731        four
 6732        five
 6733        #
 6734        # 6ˇ
 6735        "
 6736    ));
 6737}
 6738
 6739#[gpui::test]
 6740async fn test_cut_line_ends(cx: &mut TestAppContext) {
 6741    init_test(cx, |_| {});
 6742
 6743    let mut cx = EditorTestContext::new(cx).await;
 6744
 6745    cx.set_state(indoc! {"The quick brownˇ"});
 6746    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 6747    cx.assert_editor_state(indoc! {"The quick brownˇ"});
 6748
 6749    cx.set_state(indoc! {"The emacs foxˇ"});
 6750    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 6751    cx.assert_editor_state(indoc! {"The emacs foxˇ"});
 6752
 6753    cx.set_state(indoc! {"
 6754        The quick« brownˇ»
 6755        fox jumps overˇ
 6756        the lazy dog"});
 6757    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6758    cx.assert_editor_state(indoc! {"
 6759        The quickˇ
 6760        ˇthe lazy dog"});
 6761
 6762    cx.set_state(indoc! {"
 6763        The quick« brownˇ»
 6764        fox jumps overˇ
 6765        the lazy dog"});
 6766    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 6767    cx.assert_editor_state(indoc! {"
 6768        The quickˇ
 6769        fox jumps overˇthe lazy dog"});
 6770
 6771    cx.set_state(indoc! {"
 6772        The quick« brownˇ»
 6773        fox jumps overˇ
 6774        the lazy dog"});
 6775    cx.update_editor(|e, window, cx| {
 6776        e.cut_to_end_of_line(
 6777            &CutToEndOfLine {
 6778                stop_at_newlines: true,
 6779            },
 6780            window,
 6781            cx,
 6782        )
 6783    });
 6784    cx.assert_editor_state(indoc! {"
 6785        The quickˇ
 6786        fox jumps overˇ
 6787        the lazy dog"});
 6788
 6789    cx.set_state(indoc! {"
 6790        The quick« brownˇ»
 6791        fox jumps overˇ
 6792        the lazy dog"});
 6793    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 6794    cx.assert_editor_state(indoc! {"
 6795        The quickˇ
 6796        fox jumps overˇthe lazy dog"});
 6797}
 6798
 6799#[gpui::test]
 6800async fn test_clipboard(cx: &mut TestAppContext) {
 6801    init_test(cx, |_| {});
 6802
 6803    let mut cx = EditorTestContext::new(cx).await;
 6804
 6805    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 6806    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6807    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 6808
 6809    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 6810    cx.set_state("two ˇfour ˇsix ˇ");
 6811    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6812    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 6813
 6814    // Paste again but with only two cursors. Since the number of cursors doesn't
 6815    // match the number of slices in the clipboard, the entire clipboard text
 6816    // is pasted at each cursor.
 6817    cx.set_state("ˇtwo one✅ four three six five ˇ");
 6818    cx.update_editor(|e, window, cx| {
 6819        e.handle_input("( ", window, cx);
 6820        e.paste(&Paste, window, cx);
 6821        e.handle_input(") ", window, cx);
 6822    });
 6823    cx.assert_editor_state(
 6824        &([
 6825            "( one✅ ",
 6826            "three ",
 6827            "five ) ˇtwo one✅ four three six five ( one✅ ",
 6828            "three ",
 6829            "five ) ˇ",
 6830        ]
 6831        .join("\n")),
 6832    );
 6833
 6834    // Cut with three selections, one of which is full-line.
 6835    cx.set_state(indoc! {"
 6836        1«2ˇ»3
 6837        4ˇ567
 6838        «8ˇ»9"});
 6839    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6840    cx.assert_editor_state(indoc! {"
 6841        1ˇ3
 6842        ˇ9"});
 6843
 6844    // Paste with three selections, noticing how the copied selection that was full-line
 6845    // gets inserted before the second cursor.
 6846    cx.set_state(indoc! {"
 6847        1ˇ3
 6848 6849        «oˇ»ne"});
 6850    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6851    cx.assert_editor_state(indoc! {"
 6852        12ˇ3
 6853        4567
 6854 6855        8ˇne"});
 6856
 6857    // Copy with a single cursor only, which writes the whole line into the clipboard.
 6858    cx.set_state(indoc! {"
 6859        The quick brown
 6860        fox juˇmps over
 6861        the lazy dog"});
 6862    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6863    assert_eq!(
 6864        cx.read_from_clipboard()
 6865            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6866        Some("fox jumps over\n".to_string())
 6867    );
 6868
 6869    // Paste with three selections, noticing how the copied full-line selection is inserted
 6870    // before the empty selections but replaces the selection that is non-empty.
 6871    cx.set_state(indoc! {"
 6872        Tˇhe quick brown
 6873        «foˇ»x jumps over
 6874        tˇhe lazy dog"});
 6875    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6876    cx.assert_editor_state(indoc! {"
 6877        fox jumps over
 6878        Tˇhe quick brown
 6879        fox jumps over
 6880        ˇx jumps over
 6881        fox jumps over
 6882        tˇhe lazy dog"});
 6883}
 6884
 6885#[gpui::test]
 6886async fn test_copy_trim(cx: &mut TestAppContext) {
 6887    init_test(cx, |_| {});
 6888
 6889    let mut cx = EditorTestContext::new(cx).await;
 6890    cx.set_state(
 6891        r#"            «for selection in selections.iter() {
 6892            let mut start = selection.start;
 6893            let mut end = selection.end;
 6894            let is_entire_line = selection.is_empty();
 6895            if is_entire_line {
 6896                start = Point::new(start.row, 0);ˇ»
 6897                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6898            }
 6899        "#,
 6900    );
 6901    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6902    assert_eq!(
 6903        cx.read_from_clipboard()
 6904            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6905        Some(
 6906            "for selection in selections.iter() {
 6907            let mut start = selection.start;
 6908            let mut end = selection.end;
 6909            let is_entire_line = selection.is_empty();
 6910            if is_entire_line {
 6911                start = Point::new(start.row, 0);"
 6912                .to_string()
 6913        ),
 6914        "Regular copying preserves all indentation selected",
 6915    );
 6916    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6917    assert_eq!(
 6918        cx.read_from_clipboard()
 6919            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6920        Some(
 6921            "for selection in selections.iter() {
 6922let mut start = selection.start;
 6923let mut end = selection.end;
 6924let is_entire_line = selection.is_empty();
 6925if is_entire_line {
 6926    start = Point::new(start.row, 0);"
 6927                .to_string()
 6928        ),
 6929        "Copying with stripping should strip all leading whitespaces"
 6930    );
 6931
 6932    cx.set_state(
 6933        r#"       «     for selection in selections.iter() {
 6934            let mut start = selection.start;
 6935            let mut end = selection.end;
 6936            let is_entire_line = selection.is_empty();
 6937            if is_entire_line {
 6938                start = Point::new(start.row, 0);ˇ»
 6939                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6940            }
 6941        "#,
 6942    );
 6943    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6944    assert_eq!(
 6945        cx.read_from_clipboard()
 6946            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6947        Some(
 6948            "     for selection in selections.iter() {
 6949            let mut start = selection.start;
 6950            let mut end = selection.end;
 6951            let is_entire_line = selection.is_empty();
 6952            if is_entire_line {
 6953                start = Point::new(start.row, 0);"
 6954                .to_string()
 6955        ),
 6956        "Regular copying preserves all indentation selected",
 6957    );
 6958    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6959    assert_eq!(
 6960        cx.read_from_clipboard()
 6961            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6962        Some(
 6963            "for selection in selections.iter() {
 6964let mut start = selection.start;
 6965let mut end = selection.end;
 6966let is_entire_line = selection.is_empty();
 6967if is_entire_line {
 6968    start = Point::new(start.row, 0);"
 6969                .to_string()
 6970        ),
 6971        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 6972    );
 6973
 6974    cx.set_state(
 6975        r#"       «ˇ     for selection in selections.iter() {
 6976            let mut start = selection.start;
 6977            let mut end = selection.end;
 6978            let is_entire_line = selection.is_empty();
 6979            if is_entire_line {
 6980                start = Point::new(start.row, 0);»
 6981                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6982            }
 6983        "#,
 6984    );
 6985    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6986    assert_eq!(
 6987        cx.read_from_clipboard()
 6988            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6989        Some(
 6990            "     for selection in selections.iter() {
 6991            let mut start = selection.start;
 6992            let mut end = selection.end;
 6993            let is_entire_line = selection.is_empty();
 6994            if is_entire_line {
 6995                start = Point::new(start.row, 0);"
 6996                .to_string()
 6997        ),
 6998        "Regular copying for reverse selection works the same",
 6999    );
 7000    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7001    assert_eq!(
 7002        cx.read_from_clipboard()
 7003            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7004        Some(
 7005            "for selection in selections.iter() {
 7006let mut start = selection.start;
 7007let mut end = selection.end;
 7008let is_entire_line = selection.is_empty();
 7009if is_entire_line {
 7010    start = Point::new(start.row, 0);"
 7011                .to_string()
 7012        ),
 7013        "Copying with stripping for reverse selection works the same"
 7014    );
 7015
 7016    cx.set_state(
 7017        r#"            for selection «in selections.iter() {
 7018            let mut start = selection.start;
 7019            let mut end = selection.end;
 7020            let is_entire_line = selection.is_empty();
 7021            if is_entire_line {
 7022                start = Point::new(start.row, 0);ˇ»
 7023                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7024            }
 7025        "#,
 7026    );
 7027    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7028    assert_eq!(
 7029        cx.read_from_clipboard()
 7030            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7031        Some(
 7032            "in selections.iter() {
 7033            let mut start = selection.start;
 7034            let mut end = selection.end;
 7035            let is_entire_line = selection.is_empty();
 7036            if is_entire_line {
 7037                start = Point::new(start.row, 0);"
 7038                .to_string()
 7039        ),
 7040        "When selecting past the indent, the copying works as usual",
 7041    );
 7042    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7043    assert_eq!(
 7044        cx.read_from_clipboard()
 7045            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7046        Some(
 7047            "in selections.iter() {
 7048            let mut start = selection.start;
 7049            let mut end = selection.end;
 7050            let is_entire_line = selection.is_empty();
 7051            if is_entire_line {
 7052                start = Point::new(start.row, 0);"
 7053                .to_string()
 7054        ),
 7055        "When selecting past the indent, nothing is trimmed"
 7056    );
 7057
 7058    cx.set_state(
 7059        r#"            «for selection in selections.iter() {
 7060            let mut start = selection.start;
 7061
 7062            let mut end = selection.end;
 7063            let is_entire_line = selection.is_empty();
 7064            if is_entire_line {
 7065                start = Point::new(start.row, 0);
 7066ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7067            }
 7068        "#,
 7069    );
 7070    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7071    assert_eq!(
 7072        cx.read_from_clipboard()
 7073            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7074        Some(
 7075            "for selection in selections.iter() {
 7076let mut start = selection.start;
 7077
 7078let mut end = selection.end;
 7079let is_entire_line = selection.is_empty();
 7080if is_entire_line {
 7081    start = Point::new(start.row, 0);
 7082"
 7083            .to_string()
 7084        ),
 7085        "Copying with stripping should ignore empty lines"
 7086    );
 7087}
 7088
 7089#[gpui::test]
 7090async fn test_paste_multiline(cx: &mut TestAppContext) {
 7091    init_test(cx, |_| {});
 7092
 7093    let mut cx = EditorTestContext::new(cx).await;
 7094    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7095
 7096    // Cut an indented block, without the leading whitespace.
 7097    cx.set_state(indoc! {"
 7098        const a: B = (
 7099            c(),
 7100            «d(
 7101                e,
 7102                f
 7103            )ˇ»
 7104        );
 7105    "});
 7106    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7107    cx.assert_editor_state(indoc! {"
 7108        const a: B = (
 7109            c(),
 7110            ˇ
 7111        );
 7112    "});
 7113
 7114    // Paste it at the same position.
 7115    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7116    cx.assert_editor_state(indoc! {"
 7117        const a: B = (
 7118            c(),
 7119            d(
 7120                e,
 7121                f
 7122 7123        );
 7124    "});
 7125
 7126    // Paste it at a line with a lower indent level.
 7127    cx.set_state(indoc! {"
 7128        ˇ
 7129        const a: B = (
 7130            c(),
 7131        );
 7132    "});
 7133    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7134    cx.assert_editor_state(indoc! {"
 7135        d(
 7136            e,
 7137            f
 7138 7139        const a: B = (
 7140            c(),
 7141        );
 7142    "});
 7143
 7144    // Cut an indented block, with the leading whitespace.
 7145    cx.set_state(indoc! {"
 7146        const a: B = (
 7147            c(),
 7148        «    d(
 7149                e,
 7150                f
 7151            )
 7152        ˇ»);
 7153    "});
 7154    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7155    cx.assert_editor_state(indoc! {"
 7156        const a: B = (
 7157            c(),
 7158        ˇ);
 7159    "});
 7160
 7161    // Paste it at the same position.
 7162    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7163    cx.assert_editor_state(indoc! {"
 7164        const a: B = (
 7165            c(),
 7166            d(
 7167                e,
 7168                f
 7169            )
 7170        ˇ);
 7171    "});
 7172
 7173    // Paste it at a line with a higher indent level.
 7174    cx.set_state(indoc! {"
 7175        const a: B = (
 7176            c(),
 7177            d(
 7178                e,
 7179 7180            )
 7181        );
 7182    "});
 7183    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7184    cx.assert_editor_state(indoc! {"
 7185        const a: B = (
 7186            c(),
 7187            d(
 7188                e,
 7189                f    d(
 7190                    e,
 7191                    f
 7192                )
 7193        ˇ
 7194            )
 7195        );
 7196    "});
 7197
 7198    // Copy an indented block, starting mid-line
 7199    cx.set_state(indoc! {"
 7200        const a: B = (
 7201            c(),
 7202            somethin«g(
 7203                e,
 7204                f
 7205            )ˇ»
 7206        );
 7207    "});
 7208    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7209
 7210    // Paste it on a line with a lower indent level
 7211    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7212    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7213    cx.assert_editor_state(indoc! {"
 7214        const a: B = (
 7215            c(),
 7216            something(
 7217                e,
 7218                f
 7219            )
 7220        );
 7221        g(
 7222            e,
 7223            f
 7224"});
 7225}
 7226
 7227#[gpui::test]
 7228async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7229    init_test(cx, |_| {});
 7230
 7231    cx.write_to_clipboard(ClipboardItem::new_string(
 7232        "    d(\n        e\n    );\n".into(),
 7233    ));
 7234
 7235    let mut cx = EditorTestContext::new(cx).await;
 7236    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7237
 7238    cx.set_state(indoc! {"
 7239        fn a() {
 7240            b();
 7241            if c() {
 7242                ˇ
 7243            }
 7244        }
 7245    "});
 7246
 7247    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7248    cx.assert_editor_state(indoc! {"
 7249        fn a() {
 7250            b();
 7251            if c() {
 7252                d(
 7253                    e
 7254                );
 7255        ˇ
 7256            }
 7257        }
 7258    "});
 7259
 7260    cx.set_state(indoc! {"
 7261        fn a() {
 7262            b();
 7263            ˇ
 7264        }
 7265    "});
 7266
 7267    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7268    cx.assert_editor_state(indoc! {"
 7269        fn a() {
 7270            b();
 7271            d(
 7272                e
 7273            );
 7274        ˇ
 7275        }
 7276    "});
 7277}
 7278
 7279#[gpui::test]
 7280fn test_select_all(cx: &mut TestAppContext) {
 7281    init_test(cx, |_| {});
 7282
 7283    let editor = cx.add_window(|window, cx| {
 7284        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7285        build_editor(buffer, window, cx)
 7286    });
 7287    _ = editor.update(cx, |editor, window, cx| {
 7288        editor.select_all(&SelectAll, window, cx);
 7289        assert_eq!(
 7290            editor.selections.display_ranges(cx),
 7291            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7292        );
 7293    });
 7294}
 7295
 7296#[gpui::test]
 7297fn test_select_line(cx: &mut TestAppContext) {
 7298    init_test(cx, |_| {});
 7299
 7300    let editor = cx.add_window(|window, cx| {
 7301        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7302        build_editor(buffer, window, cx)
 7303    });
 7304    _ = editor.update(cx, |editor, window, cx| {
 7305        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7306            s.select_display_ranges([
 7307                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7308                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7309                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7310                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7311            ])
 7312        });
 7313        editor.select_line(&SelectLine, window, cx);
 7314        assert_eq!(
 7315            editor.selections.display_ranges(cx),
 7316            vec![
 7317                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7318                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7319            ]
 7320        );
 7321    });
 7322
 7323    _ = editor.update(cx, |editor, window, cx| {
 7324        editor.select_line(&SelectLine, window, cx);
 7325        assert_eq!(
 7326            editor.selections.display_ranges(cx),
 7327            vec![
 7328                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7329                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7330            ]
 7331        );
 7332    });
 7333
 7334    _ = editor.update(cx, |editor, window, cx| {
 7335        editor.select_line(&SelectLine, window, cx);
 7336        assert_eq!(
 7337            editor.selections.display_ranges(cx),
 7338            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 7339        );
 7340    });
 7341}
 7342
 7343#[gpui::test]
 7344async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7345    init_test(cx, |_| {});
 7346    let mut cx = EditorTestContext::new(cx).await;
 7347
 7348    #[track_caller]
 7349    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7350        cx.set_state(initial_state);
 7351        cx.update_editor(|e, window, cx| {
 7352            e.split_selection_into_lines(&Default::default(), window, cx)
 7353        });
 7354        cx.assert_editor_state(expected_state);
 7355    }
 7356
 7357    // Selection starts and ends at the middle of lines, left-to-right
 7358    test(
 7359        &mut cx,
 7360        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7361        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7362    );
 7363    // Same thing, right-to-left
 7364    test(
 7365        &mut cx,
 7366        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7367        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7368    );
 7369
 7370    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7371    test(
 7372        &mut cx,
 7373        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7374        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7375    );
 7376    // Same thing, right-to-left
 7377    test(
 7378        &mut cx,
 7379        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7380        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7381    );
 7382
 7383    // Whole buffer, left-to-right, last line ends with newline
 7384    test(
 7385        &mut cx,
 7386        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7387        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7388    );
 7389    // Same thing, right-to-left
 7390    test(
 7391        &mut cx,
 7392        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 7393        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7394    );
 7395
 7396    // Starts at the end of a line, ends at the start of another
 7397    test(
 7398        &mut cx,
 7399        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 7400        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 7401    );
 7402}
 7403
 7404#[gpui::test]
 7405async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 7406    init_test(cx, |_| {});
 7407
 7408    let editor = cx.add_window(|window, cx| {
 7409        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 7410        build_editor(buffer, window, cx)
 7411    });
 7412
 7413    // setup
 7414    _ = editor.update(cx, |editor, window, cx| {
 7415        editor.fold_creases(
 7416            vec![
 7417                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7418                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7419                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7420            ],
 7421            true,
 7422            window,
 7423            cx,
 7424        );
 7425        assert_eq!(
 7426            editor.display_text(cx),
 7427            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7428        );
 7429    });
 7430
 7431    _ = editor.update(cx, |editor, window, cx| {
 7432        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7433            s.select_display_ranges([
 7434                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7435                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7436                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7437                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7438            ])
 7439        });
 7440        editor.split_selection_into_lines(&Default::default(), window, cx);
 7441        assert_eq!(
 7442            editor.display_text(cx),
 7443            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7444        );
 7445    });
 7446    EditorTestContext::for_editor(editor, cx)
 7447        .await
 7448        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7449
 7450    _ = editor.update(cx, |editor, window, cx| {
 7451        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7452            s.select_display_ranges([
 7453                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7454            ])
 7455        });
 7456        editor.split_selection_into_lines(&Default::default(), window, cx);
 7457        assert_eq!(
 7458            editor.display_text(cx),
 7459            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7460        );
 7461        assert_eq!(
 7462            editor.selections.display_ranges(cx),
 7463            [
 7464                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7465                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7466                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7467                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7468                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7469                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7470                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7471            ]
 7472        );
 7473    });
 7474    EditorTestContext::for_editor(editor, cx)
 7475        .await
 7476        .assert_editor_state(
 7477            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7478        );
 7479}
 7480
 7481#[gpui::test]
 7482async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7483    init_test(cx, |_| {});
 7484
 7485    let mut cx = EditorTestContext::new(cx).await;
 7486
 7487    cx.set_state(indoc!(
 7488        r#"abc
 7489           defˇghi
 7490
 7491           jk
 7492           nlmo
 7493           "#
 7494    ));
 7495
 7496    cx.update_editor(|editor, window, cx| {
 7497        editor.add_selection_above(&Default::default(), window, cx);
 7498    });
 7499
 7500    cx.assert_editor_state(indoc!(
 7501        r#"abcˇ
 7502           defˇghi
 7503
 7504           jk
 7505           nlmo
 7506           "#
 7507    ));
 7508
 7509    cx.update_editor(|editor, window, cx| {
 7510        editor.add_selection_above(&Default::default(), window, cx);
 7511    });
 7512
 7513    cx.assert_editor_state(indoc!(
 7514        r#"abcˇ
 7515            defˇghi
 7516
 7517            jk
 7518            nlmo
 7519            "#
 7520    ));
 7521
 7522    cx.update_editor(|editor, window, cx| {
 7523        editor.add_selection_below(&Default::default(), window, cx);
 7524    });
 7525
 7526    cx.assert_editor_state(indoc!(
 7527        r#"abc
 7528           defˇghi
 7529
 7530           jk
 7531           nlmo
 7532           "#
 7533    ));
 7534
 7535    cx.update_editor(|editor, window, cx| {
 7536        editor.undo_selection(&Default::default(), window, cx);
 7537    });
 7538
 7539    cx.assert_editor_state(indoc!(
 7540        r#"abcˇ
 7541           defˇghi
 7542
 7543           jk
 7544           nlmo
 7545           "#
 7546    ));
 7547
 7548    cx.update_editor(|editor, window, cx| {
 7549        editor.redo_selection(&Default::default(), window, cx);
 7550    });
 7551
 7552    cx.assert_editor_state(indoc!(
 7553        r#"abc
 7554           defˇghi
 7555
 7556           jk
 7557           nlmo
 7558           "#
 7559    ));
 7560
 7561    cx.update_editor(|editor, window, cx| {
 7562        editor.add_selection_below(&Default::default(), window, cx);
 7563    });
 7564
 7565    cx.assert_editor_state(indoc!(
 7566        r#"abc
 7567           defˇghi
 7568           ˇ
 7569           jk
 7570           nlmo
 7571           "#
 7572    ));
 7573
 7574    cx.update_editor(|editor, window, cx| {
 7575        editor.add_selection_below(&Default::default(), window, cx);
 7576    });
 7577
 7578    cx.assert_editor_state(indoc!(
 7579        r#"abc
 7580           defˇghi
 7581           ˇ
 7582           jkˇ
 7583           nlmo
 7584           "#
 7585    ));
 7586
 7587    cx.update_editor(|editor, window, cx| {
 7588        editor.add_selection_below(&Default::default(), window, cx);
 7589    });
 7590
 7591    cx.assert_editor_state(indoc!(
 7592        r#"abc
 7593           defˇghi
 7594           ˇ
 7595           jkˇ
 7596           nlmˇo
 7597           "#
 7598    ));
 7599
 7600    cx.update_editor(|editor, window, cx| {
 7601        editor.add_selection_below(&Default::default(), window, cx);
 7602    });
 7603
 7604    cx.assert_editor_state(indoc!(
 7605        r#"abc
 7606           defˇghi
 7607           ˇ
 7608           jkˇ
 7609           nlmˇo
 7610           ˇ"#
 7611    ));
 7612
 7613    // change selections
 7614    cx.set_state(indoc!(
 7615        r#"abc
 7616           def«ˇg»hi
 7617
 7618           jk
 7619           nlmo
 7620           "#
 7621    ));
 7622
 7623    cx.update_editor(|editor, window, cx| {
 7624        editor.add_selection_below(&Default::default(), window, cx);
 7625    });
 7626
 7627    cx.assert_editor_state(indoc!(
 7628        r#"abc
 7629           def«ˇg»hi
 7630
 7631           jk
 7632           nlm«ˇo»
 7633           "#
 7634    ));
 7635
 7636    cx.update_editor(|editor, window, cx| {
 7637        editor.add_selection_below(&Default::default(), window, cx);
 7638    });
 7639
 7640    cx.assert_editor_state(indoc!(
 7641        r#"abc
 7642           def«ˇg»hi
 7643
 7644           jk
 7645           nlm«ˇo»
 7646           "#
 7647    ));
 7648
 7649    cx.update_editor(|editor, window, cx| {
 7650        editor.add_selection_above(&Default::default(), window, cx);
 7651    });
 7652
 7653    cx.assert_editor_state(indoc!(
 7654        r#"abc
 7655           def«ˇg»hi
 7656
 7657           jk
 7658           nlmo
 7659           "#
 7660    ));
 7661
 7662    cx.update_editor(|editor, window, cx| {
 7663        editor.add_selection_above(&Default::default(), window, cx);
 7664    });
 7665
 7666    cx.assert_editor_state(indoc!(
 7667        r#"abc
 7668           def«ˇg»hi
 7669
 7670           jk
 7671           nlmo
 7672           "#
 7673    ));
 7674
 7675    // Change selections again
 7676    cx.set_state(indoc!(
 7677        r#"a«bc
 7678           defgˇ»hi
 7679
 7680           jk
 7681           nlmo
 7682           "#
 7683    ));
 7684
 7685    cx.update_editor(|editor, window, cx| {
 7686        editor.add_selection_below(&Default::default(), window, cx);
 7687    });
 7688
 7689    cx.assert_editor_state(indoc!(
 7690        r#"a«bcˇ»
 7691           d«efgˇ»hi
 7692
 7693           j«kˇ»
 7694           nlmo
 7695           "#
 7696    ));
 7697
 7698    cx.update_editor(|editor, window, cx| {
 7699        editor.add_selection_below(&Default::default(), window, cx);
 7700    });
 7701    cx.assert_editor_state(indoc!(
 7702        r#"a«bcˇ»
 7703           d«efgˇ»hi
 7704
 7705           j«kˇ»
 7706           n«lmoˇ»
 7707           "#
 7708    ));
 7709    cx.update_editor(|editor, window, cx| {
 7710        editor.add_selection_above(&Default::default(), window, cx);
 7711    });
 7712
 7713    cx.assert_editor_state(indoc!(
 7714        r#"a«bcˇ»
 7715           d«efgˇ»hi
 7716
 7717           j«kˇ»
 7718           nlmo
 7719           "#
 7720    ));
 7721
 7722    // Change selections again
 7723    cx.set_state(indoc!(
 7724        r#"abc
 7725           d«ˇefghi
 7726
 7727           jk
 7728           nlm»o
 7729           "#
 7730    ));
 7731
 7732    cx.update_editor(|editor, window, cx| {
 7733        editor.add_selection_above(&Default::default(), window, cx);
 7734    });
 7735
 7736    cx.assert_editor_state(indoc!(
 7737        r#"a«ˇbc»
 7738           d«ˇef»ghi
 7739
 7740           j«ˇk»
 7741           n«ˇlm»o
 7742           "#
 7743    ));
 7744
 7745    cx.update_editor(|editor, window, cx| {
 7746        editor.add_selection_below(&Default::default(), window, cx);
 7747    });
 7748
 7749    cx.assert_editor_state(indoc!(
 7750        r#"abc
 7751           d«ˇef»ghi
 7752
 7753           j«ˇk»
 7754           n«ˇlm»o
 7755           "#
 7756    ));
 7757}
 7758
 7759#[gpui::test]
 7760async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 7761    init_test(cx, |_| {});
 7762    let mut cx = EditorTestContext::new(cx).await;
 7763
 7764    cx.set_state(indoc!(
 7765        r#"line onˇe
 7766           liˇne two
 7767           line three
 7768           line four"#
 7769    ));
 7770
 7771    cx.update_editor(|editor, window, cx| {
 7772        editor.add_selection_below(&Default::default(), window, cx);
 7773    });
 7774
 7775    // test multiple cursors expand in the same direction
 7776    cx.assert_editor_state(indoc!(
 7777        r#"line onˇe
 7778           liˇne twˇo
 7779           liˇne three
 7780           line four"#
 7781    ));
 7782
 7783    cx.update_editor(|editor, window, cx| {
 7784        editor.add_selection_below(&Default::default(), window, cx);
 7785    });
 7786
 7787    cx.update_editor(|editor, window, cx| {
 7788        editor.add_selection_below(&Default::default(), window, cx);
 7789    });
 7790
 7791    // test multiple cursors expand below overflow
 7792    cx.assert_editor_state(indoc!(
 7793        r#"line onˇe
 7794           liˇne twˇo
 7795           liˇne thˇree
 7796           liˇne foˇur"#
 7797    ));
 7798
 7799    cx.update_editor(|editor, window, cx| {
 7800        editor.add_selection_above(&Default::default(), window, cx);
 7801    });
 7802
 7803    // test multiple cursors retrieves back correctly
 7804    cx.assert_editor_state(indoc!(
 7805        r#"line onˇe
 7806           liˇne twˇo
 7807           liˇne thˇree
 7808           line four"#
 7809    ));
 7810
 7811    cx.update_editor(|editor, window, cx| {
 7812        editor.add_selection_above(&Default::default(), window, cx);
 7813    });
 7814
 7815    cx.update_editor(|editor, window, cx| {
 7816        editor.add_selection_above(&Default::default(), window, cx);
 7817    });
 7818
 7819    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 7820    cx.assert_editor_state(indoc!(
 7821        r#"liˇne onˇe
 7822           liˇne two
 7823           line three
 7824           line four"#
 7825    ));
 7826
 7827    cx.update_editor(|editor, window, cx| {
 7828        editor.undo_selection(&Default::default(), window, cx);
 7829    });
 7830
 7831    // test undo
 7832    cx.assert_editor_state(indoc!(
 7833        r#"line onˇe
 7834           liˇne twˇo
 7835           line three
 7836           line four"#
 7837    ));
 7838
 7839    cx.update_editor(|editor, window, cx| {
 7840        editor.redo_selection(&Default::default(), window, cx);
 7841    });
 7842
 7843    // test redo
 7844    cx.assert_editor_state(indoc!(
 7845        r#"liˇne onˇe
 7846           liˇne two
 7847           line three
 7848           line four"#
 7849    ));
 7850
 7851    cx.set_state(indoc!(
 7852        r#"abcd
 7853           ef«ghˇ»
 7854           ijkl
 7855           «mˇ»nop"#
 7856    ));
 7857
 7858    cx.update_editor(|editor, window, cx| {
 7859        editor.add_selection_above(&Default::default(), window, cx);
 7860    });
 7861
 7862    // test multiple selections expand in the same direction
 7863    cx.assert_editor_state(indoc!(
 7864        r#"ab«cdˇ»
 7865           ef«ghˇ»
 7866           «iˇ»jkl
 7867           «mˇ»nop"#
 7868    ));
 7869
 7870    cx.update_editor(|editor, window, cx| {
 7871        editor.add_selection_above(&Default::default(), window, cx);
 7872    });
 7873
 7874    // test multiple selection upward overflow
 7875    cx.assert_editor_state(indoc!(
 7876        r#"ab«cdˇ»
 7877           «eˇ»f«ghˇ»
 7878           «iˇ»jkl
 7879           «mˇ»nop"#
 7880    ));
 7881
 7882    cx.update_editor(|editor, window, cx| {
 7883        editor.add_selection_below(&Default::default(), window, cx);
 7884    });
 7885
 7886    // test multiple selection retrieves back correctly
 7887    cx.assert_editor_state(indoc!(
 7888        r#"abcd
 7889           ef«ghˇ»
 7890           «iˇ»jkl
 7891           «mˇ»nop"#
 7892    ));
 7893
 7894    cx.update_editor(|editor, window, cx| {
 7895        editor.add_selection_below(&Default::default(), window, cx);
 7896    });
 7897
 7898    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 7899    cx.assert_editor_state(indoc!(
 7900        r#"abcd
 7901           ef«ghˇ»
 7902           ij«klˇ»
 7903           «mˇ»nop"#
 7904    ));
 7905
 7906    cx.update_editor(|editor, window, cx| {
 7907        editor.undo_selection(&Default::default(), window, cx);
 7908    });
 7909
 7910    // test undo
 7911    cx.assert_editor_state(indoc!(
 7912        r#"abcd
 7913           ef«ghˇ»
 7914           «iˇ»jkl
 7915           «mˇ»nop"#
 7916    ));
 7917
 7918    cx.update_editor(|editor, window, cx| {
 7919        editor.redo_selection(&Default::default(), window, cx);
 7920    });
 7921
 7922    // test redo
 7923    cx.assert_editor_state(indoc!(
 7924        r#"abcd
 7925           ef«ghˇ»
 7926           ij«klˇ»
 7927           «mˇ»nop"#
 7928    ));
 7929}
 7930
 7931#[gpui::test]
 7932async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 7933    init_test(cx, |_| {});
 7934    let mut cx = EditorTestContext::new(cx).await;
 7935
 7936    cx.set_state(indoc!(
 7937        r#"line onˇe
 7938           liˇne two
 7939           line three
 7940           line four"#
 7941    ));
 7942
 7943    cx.update_editor(|editor, window, cx| {
 7944        editor.add_selection_below(&Default::default(), window, cx);
 7945        editor.add_selection_below(&Default::default(), window, cx);
 7946        editor.add_selection_below(&Default::default(), window, cx);
 7947    });
 7948
 7949    // initial state with two multi cursor groups
 7950    cx.assert_editor_state(indoc!(
 7951        r#"line onˇe
 7952           liˇne twˇo
 7953           liˇne thˇree
 7954           liˇne foˇur"#
 7955    ));
 7956
 7957    // add single cursor in middle - simulate opt click
 7958    cx.update_editor(|editor, window, cx| {
 7959        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 7960        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 7961        editor.end_selection(window, cx);
 7962    });
 7963
 7964    cx.assert_editor_state(indoc!(
 7965        r#"line onˇe
 7966           liˇne twˇo
 7967           liˇneˇ thˇree
 7968           liˇne foˇur"#
 7969    ));
 7970
 7971    cx.update_editor(|editor, window, cx| {
 7972        editor.add_selection_above(&Default::default(), window, cx);
 7973    });
 7974
 7975    // test new added selection expands above and existing selection shrinks
 7976    cx.assert_editor_state(indoc!(
 7977        r#"line onˇe
 7978           liˇneˇ twˇo
 7979           liˇneˇ thˇree
 7980           line four"#
 7981    ));
 7982
 7983    cx.update_editor(|editor, window, cx| {
 7984        editor.add_selection_above(&Default::default(), window, cx);
 7985    });
 7986
 7987    // test new added selection expands above and existing selection shrinks
 7988    cx.assert_editor_state(indoc!(
 7989        r#"lineˇ onˇe
 7990           liˇneˇ twˇo
 7991           lineˇ three
 7992           line four"#
 7993    ));
 7994
 7995    // intial state with two selection groups
 7996    cx.set_state(indoc!(
 7997        r#"abcd
 7998           ef«ghˇ»
 7999           ijkl
 8000           «mˇ»nop"#
 8001    ));
 8002
 8003    cx.update_editor(|editor, window, cx| {
 8004        editor.add_selection_above(&Default::default(), window, cx);
 8005        editor.add_selection_above(&Default::default(), window, cx);
 8006    });
 8007
 8008    cx.assert_editor_state(indoc!(
 8009        r#"ab«cdˇ»
 8010           «eˇ»f«ghˇ»
 8011           «iˇ»jkl
 8012           «mˇ»nop"#
 8013    ));
 8014
 8015    // add single selection in middle - simulate opt drag
 8016    cx.update_editor(|editor, window, cx| {
 8017        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8018        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8019        editor.update_selection(
 8020            DisplayPoint::new(DisplayRow(2), 4),
 8021            0,
 8022            gpui::Point::<f32>::default(),
 8023            window,
 8024            cx,
 8025        );
 8026        editor.end_selection(window, cx);
 8027    });
 8028
 8029    cx.assert_editor_state(indoc!(
 8030        r#"ab«cdˇ»
 8031           «eˇ»f«ghˇ»
 8032           «iˇ»jk«lˇ»
 8033           «mˇ»nop"#
 8034    ));
 8035
 8036    cx.update_editor(|editor, window, cx| {
 8037        editor.add_selection_below(&Default::default(), window, cx);
 8038    });
 8039
 8040    // test new added selection expands below, others shrinks from above
 8041    cx.assert_editor_state(indoc!(
 8042        r#"abcd
 8043           ef«ghˇ»
 8044           «iˇ»jk«lˇ»
 8045           «mˇ»no«pˇ»"#
 8046    ));
 8047}
 8048
 8049#[gpui::test]
 8050async fn test_select_next(cx: &mut TestAppContext) {
 8051    init_test(cx, |_| {});
 8052
 8053    let mut cx = EditorTestContext::new(cx).await;
 8054    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8055
 8056    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8057        .unwrap();
 8058    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8059
 8060    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8061        .unwrap();
 8062    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8063
 8064    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8065    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8066
 8067    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8068    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8069
 8070    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8071        .unwrap();
 8072    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8073
 8074    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8075        .unwrap();
 8076    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8077
 8078    // Test selection direction should be preserved
 8079    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8080
 8081    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8082        .unwrap();
 8083    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8084}
 8085
 8086#[gpui::test]
 8087async fn test_select_all_matches(cx: &mut TestAppContext) {
 8088    init_test(cx, |_| {});
 8089
 8090    let mut cx = EditorTestContext::new(cx).await;
 8091
 8092    // Test caret-only selections
 8093    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8094    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8095        .unwrap();
 8096    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8097
 8098    // Test left-to-right selections
 8099    cx.set_state("abc\n«abcˇ»\nabc");
 8100    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8101        .unwrap();
 8102    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8103
 8104    // Test right-to-left selections
 8105    cx.set_state("abc\n«ˇabc»\nabc");
 8106    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8107        .unwrap();
 8108    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8109
 8110    // Test selecting whitespace with caret selection
 8111    cx.set_state("abc\nˇ   abc\nabc");
 8112    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8113        .unwrap();
 8114    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8115
 8116    // Test selecting whitespace with left-to-right selection
 8117    cx.set_state("abc\n«ˇ  »abc\nabc");
 8118    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8119        .unwrap();
 8120    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8121
 8122    // Test no matches with right-to-left selection
 8123    cx.set_state("abc\n«  ˇ»abc\nabc");
 8124    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8125        .unwrap();
 8126    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8127
 8128    // Test with a single word and clip_at_line_ends=true (#29823)
 8129    cx.set_state("aˇbc");
 8130    cx.update_editor(|e, window, cx| {
 8131        e.set_clip_at_line_ends(true, cx);
 8132        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8133        e.set_clip_at_line_ends(false, cx);
 8134    });
 8135    cx.assert_editor_state("«abcˇ»");
 8136}
 8137
 8138#[gpui::test]
 8139async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8140    init_test(cx, |_| {});
 8141
 8142    let mut cx = EditorTestContext::new(cx).await;
 8143
 8144    let large_body_1 = "\nd".repeat(200);
 8145    let large_body_2 = "\ne".repeat(200);
 8146
 8147    cx.set_state(&format!(
 8148        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8149    ));
 8150    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8151        let scroll_position = editor.scroll_position(cx);
 8152        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8153        scroll_position
 8154    });
 8155
 8156    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8157        .unwrap();
 8158    cx.assert_editor_state(&format!(
 8159        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8160    ));
 8161    let scroll_position_after_selection =
 8162        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8163    assert_eq!(
 8164        initial_scroll_position, scroll_position_after_selection,
 8165        "Scroll position should not change after selecting all matches"
 8166    );
 8167}
 8168
 8169#[gpui::test]
 8170async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8171    init_test(cx, |_| {});
 8172
 8173    let mut cx = EditorLspTestContext::new_rust(
 8174        lsp::ServerCapabilities {
 8175            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8176            ..Default::default()
 8177        },
 8178        cx,
 8179    )
 8180    .await;
 8181
 8182    cx.set_state(indoc! {"
 8183        line 1
 8184        line 2
 8185        linˇe 3
 8186        line 4
 8187        line 5
 8188    "});
 8189
 8190    // Make an edit
 8191    cx.update_editor(|editor, window, cx| {
 8192        editor.handle_input("X", window, cx);
 8193    });
 8194
 8195    // Move cursor to a different position
 8196    cx.update_editor(|editor, window, cx| {
 8197        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8198            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8199        });
 8200    });
 8201
 8202    cx.assert_editor_state(indoc! {"
 8203        line 1
 8204        line 2
 8205        linXe 3
 8206        line 4
 8207        liˇne 5
 8208    "});
 8209
 8210    cx.lsp
 8211        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8212            Ok(Some(vec![lsp::TextEdit::new(
 8213                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8214                "PREFIX ".to_string(),
 8215            )]))
 8216        });
 8217
 8218    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8219        .unwrap()
 8220        .await
 8221        .unwrap();
 8222
 8223    cx.assert_editor_state(indoc! {"
 8224        PREFIX line 1
 8225        line 2
 8226        linXe 3
 8227        line 4
 8228        liˇne 5
 8229    "});
 8230
 8231    // Undo formatting
 8232    cx.update_editor(|editor, window, cx| {
 8233        editor.undo(&Default::default(), window, cx);
 8234    });
 8235
 8236    // Verify cursor moved back to position after edit
 8237    cx.assert_editor_state(indoc! {"
 8238        line 1
 8239        line 2
 8240        linXˇe 3
 8241        line 4
 8242        line 5
 8243    "});
 8244}
 8245
 8246#[gpui::test]
 8247async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8248    init_test(cx, |_| {});
 8249
 8250    let mut cx = EditorTestContext::new(cx).await;
 8251
 8252    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 8253    cx.update_editor(|editor, window, cx| {
 8254        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8255    });
 8256
 8257    cx.set_state(indoc! {"
 8258        line 1
 8259        line 2
 8260        linˇe 3
 8261        line 4
 8262        line 5
 8263        line 6
 8264        line 7
 8265        line 8
 8266        line 9
 8267        line 10
 8268    "});
 8269
 8270    let snapshot = cx.buffer_snapshot();
 8271    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8272
 8273    cx.update(|_, cx| {
 8274        provider.update(cx, |provider, _| {
 8275            provider.set_edit_prediction(Some(edit_prediction::EditPrediction::Local {
 8276                id: None,
 8277                edits: vec![(edit_position..edit_position, "X".into())],
 8278                edit_preview: None,
 8279            }))
 8280        })
 8281    });
 8282
 8283    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8284    cx.update_editor(|editor, window, cx| {
 8285        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8286    });
 8287
 8288    cx.assert_editor_state(indoc! {"
 8289        line 1
 8290        line 2
 8291        lineXˇ 3
 8292        line 4
 8293        line 5
 8294        line 6
 8295        line 7
 8296        line 8
 8297        line 9
 8298        line 10
 8299    "});
 8300
 8301    cx.update_editor(|editor, window, cx| {
 8302        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8303            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8304        });
 8305    });
 8306
 8307    cx.assert_editor_state(indoc! {"
 8308        line 1
 8309        line 2
 8310        lineX 3
 8311        line 4
 8312        line 5
 8313        line 6
 8314        line 7
 8315        line 8
 8316        line 9
 8317        liˇne 10
 8318    "});
 8319
 8320    cx.update_editor(|editor, window, cx| {
 8321        editor.undo(&Default::default(), window, cx);
 8322    });
 8323
 8324    cx.assert_editor_state(indoc! {"
 8325        line 1
 8326        line 2
 8327        lineˇ 3
 8328        line 4
 8329        line 5
 8330        line 6
 8331        line 7
 8332        line 8
 8333        line 9
 8334        line 10
 8335    "});
 8336}
 8337
 8338#[gpui::test]
 8339async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8340    init_test(cx, |_| {});
 8341
 8342    let mut cx = EditorTestContext::new(cx).await;
 8343    cx.set_state(
 8344        r#"let foo = 2;
 8345lˇet foo = 2;
 8346let fooˇ = 2;
 8347let foo = 2;
 8348let foo = ˇ2;"#,
 8349    );
 8350
 8351    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8352        .unwrap();
 8353    cx.assert_editor_state(
 8354        r#"let foo = 2;
 8355«letˇ» foo = 2;
 8356let «fooˇ» = 2;
 8357let foo = 2;
 8358let foo = «2ˇ»;"#,
 8359    );
 8360
 8361    // noop for multiple selections with different contents
 8362    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8363        .unwrap();
 8364    cx.assert_editor_state(
 8365        r#"let foo = 2;
 8366«letˇ» foo = 2;
 8367let «fooˇ» = 2;
 8368let foo = 2;
 8369let foo = «2ˇ»;"#,
 8370    );
 8371
 8372    // Test last selection direction should be preserved
 8373    cx.set_state(
 8374        r#"let foo = 2;
 8375let foo = 2;
 8376let «fooˇ» = 2;
 8377let «ˇfoo» = 2;
 8378let foo = 2;"#,
 8379    );
 8380
 8381    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8382        .unwrap();
 8383    cx.assert_editor_state(
 8384        r#"let foo = 2;
 8385let foo = 2;
 8386let «fooˇ» = 2;
 8387let «ˇfoo» = 2;
 8388let «ˇfoo» = 2;"#,
 8389    );
 8390}
 8391
 8392#[gpui::test]
 8393async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8394    init_test(cx, |_| {});
 8395
 8396    let mut cx =
 8397        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8398
 8399    cx.assert_editor_state(indoc! {"
 8400        ˇbbb
 8401        ccc
 8402
 8403        bbb
 8404        ccc
 8405        "});
 8406    cx.dispatch_action(SelectPrevious::default());
 8407    cx.assert_editor_state(indoc! {"
 8408                «bbbˇ»
 8409                ccc
 8410
 8411                bbb
 8412                ccc
 8413                "});
 8414    cx.dispatch_action(SelectPrevious::default());
 8415    cx.assert_editor_state(indoc! {"
 8416                «bbbˇ»
 8417                ccc
 8418
 8419                «bbbˇ»
 8420                ccc
 8421                "});
 8422}
 8423
 8424#[gpui::test]
 8425async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8426    init_test(cx, |_| {});
 8427
 8428    let mut cx = EditorTestContext::new(cx).await;
 8429    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8430
 8431    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8432        .unwrap();
 8433    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8434
 8435    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8436        .unwrap();
 8437    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8438
 8439    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8440    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8441
 8442    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8443    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8444
 8445    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8446        .unwrap();
 8447    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 8448
 8449    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8450        .unwrap();
 8451    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8452}
 8453
 8454#[gpui::test]
 8455async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8456    init_test(cx, |_| {});
 8457
 8458    let mut cx = EditorTestContext::new(cx).await;
 8459    cx.set_state("");
 8460
 8461    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8462        .unwrap();
 8463    cx.assert_editor_state("«aˇ»");
 8464    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8465        .unwrap();
 8466    cx.assert_editor_state("«aˇ»");
 8467}
 8468
 8469#[gpui::test]
 8470async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8471    init_test(cx, |_| {});
 8472
 8473    let mut cx = EditorTestContext::new(cx).await;
 8474    cx.set_state(
 8475        r#"let foo = 2;
 8476lˇet foo = 2;
 8477let fooˇ = 2;
 8478let foo = 2;
 8479let foo = ˇ2;"#,
 8480    );
 8481
 8482    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8483        .unwrap();
 8484    cx.assert_editor_state(
 8485        r#"let foo = 2;
 8486«letˇ» foo = 2;
 8487let «fooˇ» = 2;
 8488let foo = 2;
 8489let foo = «2ˇ»;"#,
 8490    );
 8491
 8492    // noop for multiple selections with different contents
 8493    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8494        .unwrap();
 8495    cx.assert_editor_state(
 8496        r#"let foo = 2;
 8497«letˇ» foo = 2;
 8498let «fooˇ» = 2;
 8499let foo = 2;
 8500let foo = «2ˇ»;"#,
 8501    );
 8502}
 8503
 8504#[gpui::test]
 8505async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 8506    init_test(cx, |_| {});
 8507
 8508    let mut cx = EditorTestContext::new(cx).await;
 8509    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8510
 8511    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8512        .unwrap();
 8513    // selection direction is preserved
 8514    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8515
 8516    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8517        .unwrap();
 8518    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8519
 8520    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8521    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8522
 8523    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8524    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8525
 8526    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8527        .unwrap();
 8528    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 8529
 8530    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8531        .unwrap();
 8532    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 8533}
 8534
 8535#[gpui::test]
 8536async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 8537    init_test(cx, |_| {});
 8538
 8539    let language = Arc::new(Language::new(
 8540        LanguageConfig::default(),
 8541        Some(tree_sitter_rust::LANGUAGE.into()),
 8542    ));
 8543
 8544    let text = r#"
 8545        use mod1::mod2::{mod3, mod4};
 8546
 8547        fn fn_1(param1: bool, param2: &str) {
 8548            let var1 = "text";
 8549        }
 8550    "#
 8551    .unindent();
 8552
 8553    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8554    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8555    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8556
 8557    editor
 8558        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8559        .await;
 8560
 8561    editor.update_in(cx, |editor, window, cx| {
 8562        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8563            s.select_display_ranges([
 8564                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 8565                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 8566                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 8567            ]);
 8568        });
 8569        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8570    });
 8571    editor.update(cx, |editor, cx| {
 8572        assert_text_with_selections(
 8573            editor,
 8574            indoc! {r#"
 8575                use mod1::mod2::{mod3, «mod4ˇ»};
 8576
 8577                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8578                    let var1 = "«ˇtext»";
 8579                }
 8580            "#},
 8581            cx,
 8582        );
 8583    });
 8584
 8585    editor.update_in(cx, |editor, window, cx| {
 8586        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8587    });
 8588    editor.update(cx, |editor, cx| {
 8589        assert_text_with_selections(
 8590            editor,
 8591            indoc! {r#"
 8592                use mod1::mod2::«{mod3, mod4}ˇ»;
 8593
 8594                «ˇfn fn_1(param1: bool, param2: &str) {
 8595                    let var1 = "text";
 8596 8597            "#},
 8598            cx,
 8599        );
 8600    });
 8601
 8602    editor.update_in(cx, |editor, window, cx| {
 8603        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8604    });
 8605    assert_eq!(
 8606        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8607        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8608    );
 8609
 8610    // Trying to expand the selected syntax node one more time has no effect.
 8611    editor.update_in(cx, |editor, window, cx| {
 8612        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8613    });
 8614    assert_eq!(
 8615        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8616        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8617    );
 8618
 8619    editor.update_in(cx, |editor, window, cx| {
 8620        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8621    });
 8622    editor.update(cx, |editor, cx| {
 8623        assert_text_with_selections(
 8624            editor,
 8625            indoc! {r#"
 8626                use mod1::mod2::«{mod3, mod4}ˇ»;
 8627
 8628                «ˇfn fn_1(param1: bool, param2: &str) {
 8629                    let var1 = "text";
 8630 8631            "#},
 8632            cx,
 8633        );
 8634    });
 8635
 8636    editor.update_in(cx, |editor, window, cx| {
 8637        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8638    });
 8639    editor.update(cx, |editor, cx| {
 8640        assert_text_with_selections(
 8641            editor,
 8642            indoc! {r#"
 8643                use mod1::mod2::{mod3, «mod4ˇ»};
 8644
 8645                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8646                    let var1 = "«ˇtext»";
 8647                }
 8648            "#},
 8649            cx,
 8650        );
 8651    });
 8652
 8653    editor.update_in(cx, |editor, window, cx| {
 8654        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8655    });
 8656    editor.update(cx, |editor, cx| {
 8657        assert_text_with_selections(
 8658            editor,
 8659            indoc! {r#"
 8660                use mod1::mod2::{mod3, moˇd4};
 8661
 8662                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8663                    let var1 = "teˇxt";
 8664                }
 8665            "#},
 8666            cx,
 8667        );
 8668    });
 8669
 8670    // Trying to shrink the selected syntax node one more time has no effect.
 8671    editor.update_in(cx, |editor, window, cx| {
 8672        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8673    });
 8674    editor.update_in(cx, |editor, _, cx| {
 8675        assert_text_with_selections(
 8676            editor,
 8677            indoc! {r#"
 8678                use mod1::mod2::{mod3, moˇd4};
 8679
 8680                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8681                    let var1 = "teˇxt";
 8682                }
 8683            "#},
 8684            cx,
 8685        );
 8686    });
 8687
 8688    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 8689    // a fold.
 8690    editor.update_in(cx, |editor, window, cx| {
 8691        editor.fold_creases(
 8692            vec![
 8693                Crease::simple(
 8694                    Point::new(0, 21)..Point::new(0, 24),
 8695                    FoldPlaceholder::test(),
 8696                ),
 8697                Crease::simple(
 8698                    Point::new(3, 20)..Point::new(3, 22),
 8699                    FoldPlaceholder::test(),
 8700                ),
 8701            ],
 8702            true,
 8703            window,
 8704            cx,
 8705        );
 8706        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8707    });
 8708    editor.update(cx, |editor, cx| {
 8709        assert_text_with_selections(
 8710            editor,
 8711            indoc! {r#"
 8712                use mod1::mod2::«{mod3, mod4}ˇ»;
 8713
 8714                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8715                    let var1 = "«ˇtext»";
 8716                }
 8717            "#},
 8718            cx,
 8719        );
 8720    });
 8721}
 8722
 8723#[gpui::test]
 8724async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 8725    init_test(cx, |_| {});
 8726
 8727    let language = Arc::new(Language::new(
 8728        LanguageConfig::default(),
 8729        Some(tree_sitter_rust::LANGUAGE.into()),
 8730    ));
 8731
 8732    let text = "let a = 2;";
 8733
 8734    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8735    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8736    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8737
 8738    editor
 8739        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8740        .await;
 8741
 8742    // Test case 1: Cursor at end of word
 8743    editor.update_in(cx, |editor, window, cx| {
 8744        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8745            s.select_display_ranges([
 8746                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 8747            ]);
 8748        });
 8749    });
 8750    editor.update(cx, |editor, cx| {
 8751        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 8752    });
 8753    editor.update_in(cx, |editor, window, cx| {
 8754        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8755    });
 8756    editor.update(cx, |editor, cx| {
 8757        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 8758    });
 8759    editor.update_in(cx, |editor, window, cx| {
 8760        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8761    });
 8762    editor.update(cx, |editor, cx| {
 8763        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 8764    });
 8765
 8766    // Test case 2: Cursor at end of statement
 8767    editor.update_in(cx, |editor, window, cx| {
 8768        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8769            s.select_display_ranges([
 8770                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 8771            ]);
 8772        });
 8773    });
 8774    editor.update(cx, |editor, cx| {
 8775        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 8776    });
 8777    editor.update_in(cx, |editor, window, cx| {
 8778        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8779    });
 8780    editor.update(cx, |editor, cx| {
 8781        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 8782    });
 8783}
 8784
 8785#[gpui::test]
 8786async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 8787    init_test(cx, |_| {});
 8788
 8789    let language = Arc::new(Language::new(
 8790        LanguageConfig {
 8791            name: "JavaScript".into(),
 8792            ..Default::default()
 8793        },
 8794        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8795    ));
 8796
 8797    let text = r#"
 8798        let a = {
 8799            key: "value",
 8800        };
 8801    "#
 8802    .unindent();
 8803
 8804    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8805    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8806    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8807
 8808    editor
 8809        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8810        .await;
 8811
 8812    // Test case 1: Cursor after '{'
 8813    editor.update_in(cx, |editor, window, cx| {
 8814        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8815            s.select_display_ranges([
 8816                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 8817            ]);
 8818        });
 8819    });
 8820    editor.update(cx, |editor, cx| {
 8821        assert_text_with_selections(
 8822            editor,
 8823            indoc! {r#"
 8824                let a = {ˇ
 8825                    key: "value",
 8826                };
 8827            "#},
 8828            cx,
 8829        );
 8830    });
 8831    editor.update_in(cx, |editor, window, cx| {
 8832        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8833    });
 8834    editor.update(cx, |editor, cx| {
 8835        assert_text_with_selections(
 8836            editor,
 8837            indoc! {r#"
 8838                let a = «ˇ{
 8839                    key: "value",
 8840                }»;
 8841            "#},
 8842            cx,
 8843        );
 8844    });
 8845
 8846    // Test case 2: Cursor after ':'
 8847    editor.update_in(cx, |editor, window, cx| {
 8848        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8849            s.select_display_ranges([
 8850                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 8851            ]);
 8852        });
 8853    });
 8854    editor.update(cx, |editor, cx| {
 8855        assert_text_with_selections(
 8856            editor,
 8857            indoc! {r#"
 8858                let a = {
 8859                    key:ˇ "value",
 8860                };
 8861            "#},
 8862            cx,
 8863        );
 8864    });
 8865    editor.update_in(cx, |editor, window, cx| {
 8866        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8867    });
 8868    editor.update(cx, |editor, cx| {
 8869        assert_text_with_selections(
 8870            editor,
 8871            indoc! {r#"
 8872                let a = {
 8873                    «ˇkey: "value"»,
 8874                };
 8875            "#},
 8876            cx,
 8877        );
 8878    });
 8879    editor.update_in(cx, |editor, window, cx| {
 8880        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8881    });
 8882    editor.update(cx, |editor, cx| {
 8883        assert_text_with_selections(
 8884            editor,
 8885            indoc! {r#"
 8886                let a = «ˇ{
 8887                    key: "value",
 8888                }»;
 8889            "#},
 8890            cx,
 8891        );
 8892    });
 8893
 8894    // Test case 3: Cursor after ','
 8895    editor.update_in(cx, |editor, window, cx| {
 8896        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8897            s.select_display_ranges([
 8898                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 8899            ]);
 8900        });
 8901    });
 8902    editor.update(cx, |editor, cx| {
 8903        assert_text_with_selections(
 8904            editor,
 8905            indoc! {r#"
 8906                let a = {
 8907                    key: "value",ˇ
 8908                };
 8909            "#},
 8910            cx,
 8911        );
 8912    });
 8913    editor.update_in(cx, |editor, window, cx| {
 8914        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8915    });
 8916    editor.update(cx, |editor, cx| {
 8917        assert_text_with_selections(
 8918            editor,
 8919            indoc! {r#"
 8920                let a = «ˇ{
 8921                    key: "value",
 8922                }»;
 8923            "#},
 8924            cx,
 8925        );
 8926    });
 8927
 8928    // Test case 4: Cursor after ';'
 8929    editor.update_in(cx, |editor, window, cx| {
 8930        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8931            s.select_display_ranges([
 8932                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 8933            ]);
 8934        });
 8935    });
 8936    editor.update(cx, |editor, cx| {
 8937        assert_text_with_selections(
 8938            editor,
 8939            indoc! {r#"
 8940                let a = {
 8941                    key: "value",
 8942                };ˇ
 8943            "#},
 8944            cx,
 8945        );
 8946    });
 8947    editor.update_in(cx, |editor, window, cx| {
 8948        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8949    });
 8950    editor.update(cx, |editor, cx| {
 8951        assert_text_with_selections(
 8952            editor,
 8953            indoc! {r#"
 8954                «ˇlet a = {
 8955                    key: "value",
 8956                };
 8957                »"#},
 8958            cx,
 8959        );
 8960    });
 8961}
 8962
 8963#[gpui::test]
 8964async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 8965    init_test(cx, |_| {});
 8966
 8967    let language = Arc::new(Language::new(
 8968        LanguageConfig::default(),
 8969        Some(tree_sitter_rust::LANGUAGE.into()),
 8970    ));
 8971
 8972    let text = r#"
 8973        use mod1::mod2::{mod3, mod4};
 8974
 8975        fn fn_1(param1: bool, param2: &str) {
 8976            let var1 = "hello world";
 8977        }
 8978    "#
 8979    .unindent();
 8980
 8981    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8982    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8983    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8984
 8985    editor
 8986        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8987        .await;
 8988
 8989    // Test 1: Cursor on a letter of a string word
 8990    editor.update_in(cx, |editor, window, cx| {
 8991        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8992            s.select_display_ranges([
 8993                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 8994            ]);
 8995        });
 8996    });
 8997    editor.update_in(cx, |editor, window, cx| {
 8998        assert_text_with_selections(
 8999            editor,
 9000            indoc! {r#"
 9001                use mod1::mod2::{mod3, mod4};
 9002
 9003                fn fn_1(param1: bool, param2: &str) {
 9004                    let var1 = "hˇello world";
 9005                }
 9006            "#},
 9007            cx,
 9008        );
 9009        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9010        assert_text_with_selections(
 9011            editor,
 9012            indoc! {r#"
 9013                use mod1::mod2::{mod3, mod4};
 9014
 9015                fn fn_1(param1: bool, param2: &str) {
 9016                    let var1 = "«ˇhello» world";
 9017                }
 9018            "#},
 9019            cx,
 9020        );
 9021    });
 9022
 9023    // Test 2: Partial selection within a word
 9024    editor.update_in(cx, |editor, window, cx| {
 9025        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9026            s.select_display_ranges([
 9027                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9028            ]);
 9029        });
 9030    });
 9031    editor.update_in(cx, |editor, window, cx| {
 9032        assert_text_with_selections(
 9033            editor,
 9034            indoc! {r#"
 9035                use mod1::mod2::{mod3, mod4};
 9036
 9037                fn fn_1(param1: bool, param2: &str) {
 9038                    let var1 = "h«elˇ»lo world";
 9039                }
 9040            "#},
 9041            cx,
 9042        );
 9043        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9044        assert_text_with_selections(
 9045            editor,
 9046            indoc! {r#"
 9047                use mod1::mod2::{mod3, mod4};
 9048
 9049                fn fn_1(param1: bool, param2: &str) {
 9050                    let var1 = "«ˇhello» world";
 9051                }
 9052            "#},
 9053            cx,
 9054        );
 9055    });
 9056
 9057    // Test 3: Complete word already selected
 9058    editor.update_in(cx, |editor, window, cx| {
 9059        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9060            s.select_display_ranges([
 9061                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9062            ]);
 9063        });
 9064    });
 9065    editor.update_in(cx, |editor, window, cx| {
 9066        assert_text_with_selections(
 9067            editor,
 9068            indoc! {r#"
 9069                use mod1::mod2::{mod3, mod4};
 9070
 9071                fn fn_1(param1: bool, param2: &str) {
 9072                    let var1 = "«helloˇ» world";
 9073                }
 9074            "#},
 9075            cx,
 9076        );
 9077        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9078        assert_text_with_selections(
 9079            editor,
 9080            indoc! {r#"
 9081                use mod1::mod2::{mod3, mod4};
 9082
 9083                fn fn_1(param1: bool, param2: &str) {
 9084                    let var1 = "«hello worldˇ»";
 9085                }
 9086            "#},
 9087            cx,
 9088        );
 9089    });
 9090
 9091    // Test 4: Selection spanning across words
 9092    editor.update_in(cx, |editor, window, cx| {
 9093        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9094            s.select_display_ranges([
 9095                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9096            ]);
 9097        });
 9098    });
 9099    editor.update_in(cx, |editor, window, cx| {
 9100        assert_text_with_selections(
 9101            editor,
 9102            indoc! {r#"
 9103                use mod1::mod2::{mod3, mod4};
 9104
 9105                fn fn_1(param1: bool, param2: &str) {
 9106                    let var1 = "hel«lo woˇ»rld";
 9107                }
 9108            "#},
 9109            cx,
 9110        );
 9111        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9112        assert_text_with_selections(
 9113            editor,
 9114            indoc! {r#"
 9115                use mod1::mod2::{mod3, mod4};
 9116
 9117                fn fn_1(param1: bool, param2: &str) {
 9118                    let var1 = "«ˇhello world»";
 9119                }
 9120            "#},
 9121            cx,
 9122        );
 9123    });
 9124
 9125    // Test 5: Expansion beyond string
 9126    editor.update_in(cx, |editor, window, cx| {
 9127        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9128        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9129        assert_text_with_selections(
 9130            editor,
 9131            indoc! {r#"
 9132                use mod1::mod2::{mod3, mod4};
 9133
 9134                fn fn_1(param1: bool, param2: &str) {
 9135                    «ˇlet var1 = "hello world";»
 9136                }
 9137            "#},
 9138            cx,
 9139        );
 9140    });
 9141}
 9142
 9143#[gpui::test]
 9144async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9145    init_test(cx, |_| {});
 9146
 9147    let mut cx = EditorTestContext::new(cx).await;
 9148
 9149    let language = Arc::new(Language::new(
 9150        LanguageConfig::default(),
 9151        Some(tree_sitter_rust::LANGUAGE.into()),
 9152    ));
 9153
 9154    cx.update_buffer(|buffer, cx| {
 9155        buffer.set_language(Some(language), cx);
 9156    });
 9157
 9158    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9159    cx.update_editor(|editor, window, cx| {
 9160        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9161    });
 9162
 9163    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9164}
 9165
 9166#[gpui::test]
 9167async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9168    init_test(cx, |_| {});
 9169
 9170    let base_text = r#"
 9171        impl A {
 9172            // this is an uncommitted comment
 9173
 9174            fn b() {
 9175                c();
 9176            }
 9177
 9178            // this is another uncommitted comment
 9179
 9180            fn d() {
 9181                // e
 9182                // f
 9183            }
 9184        }
 9185
 9186        fn g() {
 9187            // h
 9188        }
 9189    "#
 9190    .unindent();
 9191
 9192    let text = r#"
 9193        ˇimpl A {
 9194
 9195            fn b() {
 9196                c();
 9197            }
 9198
 9199            fn d() {
 9200                // e
 9201                // f
 9202            }
 9203        }
 9204
 9205        fn g() {
 9206            // h
 9207        }
 9208    "#
 9209    .unindent();
 9210
 9211    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9212    cx.set_state(&text);
 9213    cx.set_head_text(&base_text);
 9214    cx.update_editor(|editor, window, cx| {
 9215        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9216    });
 9217
 9218    cx.assert_state_with_diff(
 9219        "
 9220        ˇimpl A {
 9221      -     // this is an uncommitted comment
 9222
 9223            fn b() {
 9224                c();
 9225            }
 9226
 9227      -     // this is another uncommitted comment
 9228      -
 9229            fn d() {
 9230                // e
 9231                // f
 9232            }
 9233        }
 9234
 9235        fn g() {
 9236            // h
 9237        }
 9238    "
 9239        .unindent(),
 9240    );
 9241
 9242    let expected_display_text = "
 9243        impl A {
 9244            // this is an uncommitted comment
 9245
 9246            fn b() {
 9247 9248            }
 9249
 9250            // this is another uncommitted comment
 9251
 9252            fn d() {
 9253 9254            }
 9255        }
 9256
 9257        fn g() {
 9258 9259        }
 9260        "
 9261    .unindent();
 9262
 9263    cx.update_editor(|editor, window, cx| {
 9264        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9265        assert_eq!(editor.display_text(cx), expected_display_text);
 9266    });
 9267}
 9268
 9269#[gpui::test]
 9270async fn test_autoindent(cx: &mut TestAppContext) {
 9271    init_test(cx, |_| {});
 9272
 9273    let language = Arc::new(
 9274        Language::new(
 9275            LanguageConfig {
 9276                brackets: BracketPairConfig {
 9277                    pairs: vec![
 9278                        BracketPair {
 9279                            start: "{".to_string(),
 9280                            end: "}".to_string(),
 9281                            close: false,
 9282                            surround: false,
 9283                            newline: true,
 9284                        },
 9285                        BracketPair {
 9286                            start: "(".to_string(),
 9287                            end: ")".to_string(),
 9288                            close: false,
 9289                            surround: false,
 9290                            newline: true,
 9291                        },
 9292                    ],
 9293                    ..Default::default()
 9294                },
 9295                ..Default::default()
 9296            },
 9297            Some(tree_sitter_rust::LANGUAGE.into()),
 9298        )
 9299        .with_indents_query(
 9300            r#"
 9301                (_ "(" ")" @end) @indent
 9302                (_ "{" "}" @end) @indent
 9303            "#,
 9304        )
 9305        .unwrap(),
 9306    );
 9307
 9308    let text = "fn a() {}";
 9309
 9310    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9311    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9312    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9313    editor
 9314        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9315        .await;
 9316
 9317    editor.update_in(cx, |editor, window, cx| {
 9318        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9319            s.select_ranges([5..5, 8..8, 9..9])
 9320        });
 9321        editor.newline(&Newline, window, cx);
 9322        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9323        assert_eq!(
 9324            editor.selections.ranges(cx),
 9325            &[
 9326                Point::new(1, 4)..Point::new(1, 4),
 9327                Point::new(3, 4)..Point::new(3, 4),
 9328                Point::new(5, 0)..Point::new(5, 0)
 9329            ]
 9330        );
 9331    });
 9332}
 9333
 9334#[gpui::test]
 9335async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9336    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9337
 9338    let language = Arc::new(
 9339        Language::new(
 9340            LanguageConfig {
 9341                brackets: BracketPairConfig {
 9342                    pairs: vec![
 9343                        BracketPair {
 9344                            start: "{".to_string(),
 9345                            end: "}".to_string(),
 9346                            close: false,
 9347                            surround: false,
 9348                            newline: true,
 9349                        },
 9350                        BracketPair {
 9351                            start: "(".to_string(),
 9352                            end: ")".to_string(),
 9353                            close: false,
 9354                            surround: false,
 9355                            newline: true,
 9356                        },
 9357                    ],
 9358                    ..Default::default()
 9359                },
 9360                ..Default::default()
 9361            },
 9362            Some(tree_sitter_rust::LANGUAGE.into()),
 9363        )
 9364        .with_indents_query(
 9365            r#"
 9366                (_ "(" ")" @end) @indent
 9367                (_ "{" "}" @end) @indent
 9368            "#,
 9369        )
 9370        .unwrap(),
 9371    );
 9372
 9373    let text = "fn a() {}";
 9374
 9375    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9376    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9377    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9378    editor
 9379        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9380        .await;
 9381
 9382    editor.update_in(cx, |editor, window, cx| {
 9383        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9384            s.select_ranges([5..5, 8..8, 9..9])
 9385        });
 9386        editor.newline(&Newline, window, cx);
 9387        assert_eq!(
 9388            editor.text(cx),
 9389            indoc!(
 9390                "
 9391                fn a(
 9392
 9393                ) {
 9394
 9395                }
 9396                "
 9397            )
 9398        );
 9399        assert_eq!(
 9400            editor.selections.ranges(cx),
 9401            &[
 9402                Point::new(1, 0)..Point::new(1, 0),
 9403                Point::new(3, 0)..Point::new(3, 0),
 9404                Point::new(5, 0)..Point::new(5, 0)
 9405            ]
 9406        );
 9407    });
 9408}
 9409
 9410#[gpui::test]
 9411async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 9412    init_test(cx, |settings| {
 9413        settings.defaults.auto_indent = Some(true);
 9414        settings.languages.0.insert(
 9415            "python".into(),
 9416            LanguageSettingsContent {
 9417                auto_indent: Some(false),
 9418                ..Default::default()
 9419            },
 9420        );
 9421    });
 9422
 9423    let mut cx = EditorTestContext::new(cx).await;
 9424
 9425    let injected_language = Arc::new(
 9426        Language::new(
 9427            LanguageConfig {
 9428                brackets: BracketPairConfig {
 9429                    pairs: vec![
 9430                        BracketPair {
 9431                            start: "{".to_string(),
 9432                            end: "}".to_string(),
 9433                            close: false,
 9434                            surround: false,
 9435                            newline: true,
 9436                        },
 9437                        BracketPair {
 9438                            start: "(".to_string(),
 9439                            end: ")".to_string(),
 9440                            close: true,
 9441                            surround: false,
 9442                            newline: true,
 9443                        },
 9444                    ],
 9445                    ..Default::default()
 9446                },
 9447                name: "python".into(),
 9448                ..Default::default()
 9449            },
 9450            Some(tree_sitter_python::LANGUAGE.into()),
 9451        )
 9452        .with_indents_query(
 9453            r#"
 9454                (_ "(" ")" @end) @indent
 9455                (_ "{" "}" @end) @indent
 9456            "#,
 9457        )
 9458        .unwrap(),
 9459    );
 9460
 9461    let language = Arc::new(
 9462        Language::new(
 9463            LanguageConfig {
 9464                brackets: BracketPairConfig {
 9465                    pairs: vec![
 9466                        BracketPair {
 9467                            start: "{".to_string(),
 9468                            end: "}".to_string(),
 9469                            close: false,
 9470                            surround: false,
 9471                            newline: true,
 9472                        },
 9473                        BracketPair {
 9474                            start: "(".to_string(),
 9475                            end: ")".to_string(),
 9476                            close: true,
 9477                            surround: false,
 9478                            newline: true,
 9479                        },
 9480                    ],
 9481                    ..Default::default()
 9482                },
 9483                name: LanguageName::new("rust"),
 9484                ..Default::default()
 9485            },
 9486            Some(tree_sitter_rust::LANGUAGE.into()),
 9487        )
 9488        .with_indents_query(
 9489            r#"
 9490                (_ "(" ")" @end) @indent
 9491                (_ "{" "}" @end) @indent
 9492            "#,
 9493        )
 9494        .unwrap()
 9495        .with_injection_query(
 9496            r#"
 9497            (macro_invocation
 9498                macro: (identifier) @_macro_name
 9499                (token_tree) @injection.content
 9500                (#set! injection.language "python"))
 9501           "#,
 9502        )
 9503        .unwrap(),
 9504    );
 9505
 9506    cx.language_registry().add(injected_language);
 9507    cx.language_registry().add(language.clone());
 9508
 9509    cx.update_buffer(|buffer, cx| {
 9510        buffer.set_language(Some(language), cx);
 9511    });
 9512
 9513    cx.set_state(r#"struct A {ˇ}"#);
 9514
 9515    cx.update_editor(|editor, window, cx| {
 9516        editor.newline(&Default::default(), window, cx);
 9517    });
 9518
 9519    cx.assert_editor_state(indoc!(
 9520        "struct A {
 9521            ˇ
 9522        }"
 9523    ));
 9524
 9525    cx.set_state(r#"select_biased!(ˇ)"#);
 9526
 9527    cx.update_editor(|editor, window, cx| {
 9528        editor.newline(&Default::default(), window, cx);
 9529        editor.handle_input("def ", window, cx);
 9530        editor.handle_input("(", window, cx);
 9531        editor.newline(&Default::default(), window, cx);
 9532        editor.handle_input("a", window, cx);
 9533    });
 9534
 9535    cx.assert_editor_state(indoc!(
 9536        "select_biased!(
 9537        def (
 9538 9539        )
 9540        )"
 9541    ));
 9542}
 9543
 9544#[gpui::test]
 9545async fn test_autoindent_selections(cx: &mut TestAppContext) {
 9546    init_test(cx, |_| {});
 9547
 9548    {
 9549        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9550        cx.set_state(indoc! {"
 9551            impl A {
 9552
 9553                fn b() {}
 9554
 9555            «fn c() {
 9556
 9557            }ˇ»
 9558            }
 9559        "});
 9560
 9561        cx.update_editor(|editor, window, cx| {
 9562            editor.autoindent(&Default::default(), window, cx);
 9563        });
 9564
 9565        cx.assert_editor_state(indoc! {"
 9566            impl A {
 9567
 9568                fn b() {}
 9569
 9570                «fn c() {
 9571
 9572                }ˇ»
 9573            }
 9574        "});
 9575    }
 9576
 9577    {
 9578        let mut cx = EditorTestContext::new_multibuffer(
 9579            cx,
 9580            [indoc! { "
 9581                impl A {
 9582                «
 9583                // a
 9584                fn b(){}
 9585                »
 9586                «
 9587                    }
 9588                    fn c(){}
 9589                »
 9590            "}],
 9591        );
 9592
 9593        let buffer = cx.update_editor(|editor, _, cx| {
 9594            let buffer = editor.buffer().update(cx, |buffer, _| {
 9595                buffer.all_buffers().iter().next().unwrap().clone()
 9596            });
 9597            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 9598            buffer
 9599        });
 9600
 9601        cx.run_until_parked();
 9602        cx.update_editor(|editor, window, cx| {
 9603            editor.select_all(&Default::default(), window, cx);
 9604            editor.autoindent(&Default::default(), window, cx)
 9605        });
 9606        cx.run_until_parked();
 9607
 9608        cx.update(|_, cx| {
 9609            assert_eq!(
 9610                buffer.read(cx).text(),
 9611                indoc! { "
 9612                    impl A {
 9613
 9614                        // a
 9615                        fn b(){}
 9616
 9617
 9618                    }
 9619                    fn c(){}
 9620
 9621                " }
 9622            )
 9623        });
 9624    }
 9625}
 9626
 9627#[gpui::test]
 9628async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 9629    init_test(cx, |_| {});
 9630
 9631    let mut cx = EditorTestContext::new(cx).await;
 9632
 9633    let language = Arc::new(Language::new(
 9634        LanguageConfig {
 9635            brackets: BracketPairConfig {
 9636                pairs: vec![
 9637                    BracketPair {
 9638                        start: "{".to_string(),
 9639                        end: "}".to_string(),
 9640                        close: true,
 9641                        surround: true,
 9642                        newline: true,
 9643                    },
 9644                    BracketPair {
 9645                        start: "(".to_string(),
 9646                        end: ")".to_string(),
 9647                        close: true,
 9648                        surround: true,
 9649                        newline: true,
 9650                    },
 9651                    BracketPair {
 9652                        start: "/*".to_string(),
 9653                        end: " */".to_string(),
 9654                        close: true,
 9655                        surround: true,
 9656                        newline: true,
 9657                    },
 9658                    BracketPair {
 9659                        start: "[".to_string(),
 9660                        end: "]".to_string(),
 9661                        close: false,
 9662                        surround: false,
 9663                        newline: true,
 9664                    },
 9665                    BracketPair {
 9666                        start: "\"".to_string(),
 9667                        end: "\"".to_string(),
 9668                        close: true,
 9669                        surround: true,
 9670                        newline: false,
 9671                    },
 9672                    BracketPair {
 9673                        start: "<".to_string(),
 9674                        end: ">".to_string(),
 9675                        close: false,
 9676                        surround: true,
 9677                        newline: true,
 9678                    },
 9679                ],
 9680                ..Default::default()
 9681            },
 9682            autoclose_before: "})]".to_string(),
 9683            ..Default::default()
 9684        },
 9685        Some(tree_sitter_rust::LANGUAGE.into()),
 9686    ));
 9687
 9688    cx.language_registry().add(language.clone());
 9689    cx.update_buffer(|buffer, cx| {
 9690        buffer.set_language(Some(language), cx);
 9691    });
 9692
 9693    cx.set_state(
 9694        &r#"
 9695            🏀ˇ
 9696            εˇ
 9697            ❤️ˇ
 9698        "#
 9699        .unindent(),
 9700    );
 9701
 9702    // autoclose multiple nested brackets at multiple cursors
 9703    cx.update_editor(|editor, window, cx| {
 9704        editor.handle_input("{", window, cx);
 9705        editor.handle_input("{", window, cx);
 9706        editor.handle_input("{", window, cx);
 9707    });
 9708    cx.assert_editor_state(
 9709        &"
 9710            🏀{{{ˇ}}}
 9711            ε{{{ˇ}}}
 9712            ❤️{{{ˇ}}}
 9713        "
 9714        .unindent(),
 9715    );
 9716
 9717    // insert a different closing bracket
 9718    cx.update_editor(|editor, window, cx| {
 9719        editor.handle_input(")", window, cx);
 9720    });
 9721    cx.assert_editor_state(
 9722        &"
 9723            🏀{{{)ˇ}}}
 9724            ε{{{)ˇ}}}
 9725            ❤️{{{)ˇ}}}
 9726        "
 9727        .unindent(),
 9728    );
 9729
 9730    // skip over the auto-closed brackets when typing a closing bracket
 9731    cx.update_editor(|editor, window, cx| {
 9732        editor.move_right(&MoveRight, window, cx);
 9733        editor.handle_input("}", window, cx);
 9734        editor.handle_input("}", window, cx);
 9735        editor.handle_input("}", window, cx);
 9736    });
 9737    cx.assert_editor_state(
 9738        &"
 9739            🏀{{{)}}}}ˇ
 9740            ε{{{)}}}}ˇ
 9741            ❤️{{{)}}}}ˇ
 9742        "
 9743        .unindent(),
 9744    );
 9745
 9746    // autoclose multi-character pairs
 9747    cx.set_state(
 9748        &"
 9749            ˇ
 9750            ˇ
 9751        "
 9752        .unindent(),
 9753    );
 9754    cx.update_editor(|editor, window, cx| {
 9755        editor.handle_input("/", window, cx);
 9756        editor.handle_input("*", window, cx);
 9757    });
 9758    cx.assert_editor_state(
 9759        &"
 9760            /*ˇ */
 9761            /*ˇ */
 9762        "
 9763        .unindent(),
 9764    );
 9765
 9766    // one cursor autocloses a multi-character pair, one cursor
 9767    // does not autoclose.
 9768    cx.set_state(
 9769        &"
 9770 9771            ˇ
 9772        "
 9773        .unindent(),
 9774    );
 9775    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 9776    cx.assert_editor_state(
 9777        &"
 9778            /*ˇ */
 9779 9780        "
 9781        .unindent(),
 9782    );
 9783
 9784    // Don't autoclose if the next character isn't whitespace and isn't
 9785    // listed in the language's "autoclose_before" section.
 9786    cx.set_state("ˇa b");
 9787    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9788    cx.assert_editor_state("{ˇa b");
 9789
 9790    // Don't autoclose if `close` is false for the bracket pair
 9791    cx.set_state("ˇ");
 9792    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 9793    cx.assert_editor_state("");
 9794
 9795    // Surround with brackets if text is selected
 9796    cx.set_state("«aˇ» b");
 9797    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9798    cx.assert_editor_state("{«aˇ»} b");
 9799
 9800    // Autoclose when not immediately after a word character
 9801    cx.set_state("a ˇ");
 9802    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9803    cx.assert_editor_state("a \"ˇ\"");
 9804
 9805    // Autoclose pair where the start and end characters are the same
 9806    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9807    cx.assert_editor_state("a \"\"ˇ");
 9808
 9809    // Don't autoclose when immediately after a word character
 9810    cx.set_state("");
 9811    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9812    cx.assert_editor_state("a\"ˇ");
 9813
 9814    // Do autoclose when after a non-word character
 9815    cx.set_state("");
 9816    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9817    cx.assert_editor_state("{\"ˇ\"");
 9818
 9819    // Non identical pairs autoclose regardless of preceding character
 9820    cx.set_state("");
 9821    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9822    cx.assert_editor_state("a{ˇ}");
 9823
 9824    // Don't autoclose pair if autoclose is disabled
 9825    cx.set_state("ˇ");
 9826    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9827    cx.assert_editor_state("");
 9828
 9829    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 9830    cx.set_state("«aˇ» b");
 9831    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9832    cx.assert_editor_state("<«aˇ»> b");
 9833}
 9834
 9835#[gpui::test]
 9836async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 9837    init_test(cx, |settings| {
 9838        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9839    });
 9840
 9841    let mut cx = EditorTestContext::new(cx).await;
 9842
 9843    let language = Arc::new(Language::new(
 9844        LanguageConfig {
 9845            brackets: BracketPairConfig {
 9846                pairs: vec![
 9847                    BracketPair {
 9848                        start: "{".to_string(),
 9849                        end: "}".to_string(),
 9850                        close: true,
 9851                        surround: true,
 9852                        newline: true,
 9853                    },
 9854                    BracketPair {
 9855                        start: "(".to_string(),
 9856                        end: ")".to_string(),
 9857                        close: true,
 9858                        surround: true,
 9859                        newline: true,
 9860                    },
 9861                    BracketPair {
 9862                        start: "[".to_string(),
 9863                        end: "]".to_string(),
 9864                        close: false,
 9865                        surround: false,
 9866                        newline: true,
 9867                    },
 9868                ],
 9869                ..Default::default()
 9870            },
 9871            autoclose_before: "})]".to_string(),
 9872            ..Default::default()
 9873        },
 9874        Some(tree_sitter_rust::LANGUAGE.into()),
 9875    ));
 9876
 9877    cx.language_registry().add(language.clone());
 9878    cx.update_buffer(|buffer, cx| {
 9879        buffer.set_language(Some(language), cx);
 9880    });
 9881
 9882    cx.set_state(
 9883        &"
 9884            ˇ
 9885            ˇ
 9886            ˇ
 9887        "
 9888        .unindent(),
 9889    );
 9890
 9891    // ensure only matching closing brackets are skipped over
 9892    cx.update_editor(|editor, window, cx| {
 9893        editor.handle_input("}", window, cx);
 9894        editor.move_left(&MoveLeft, window, cx);
 9895        editor.handle_input(")", window, cx);
 9896        editor.move_left(&MoveLeft, window, cx);
 9897    });
 9898    cx.assert_editor_state(
 9899        &"
 9900            ˇ)}
 9901            ˇ)}
 9902            ˇ)}
 9903        "
 9904        .unindent(),
 9905    );
 9906
 9907    // skip-over closing brackets at multiple cursors
 9908    cx.update_editor(|editor, window, cx| {
 9909        editor.handle_input(")", window, cx);
 9910        editor.handle_input("}", window, cx);
 9911    });
 9912    cx.assert_editor_state(
 9913        &"
 9914            )}ˇ
 9915            )}ˇ
 9916            )}ˇ
 9917        "
 9918        .unindent(),
 9919    );
 9920
 9921    // ignore non-close brackets
 9922    cx.update_editor(|editor, window, cx| {
 9923        editor.handle_input("]", window, cx);
 9924        editor.move_left(&MoveLeft, window, cx);
 9925        editor.handle_input("]", window, cx);
 9926    });
 9927    cx.assert_editor_state(
 9928        &"
 9929            )}]ˇ]
 9930            )}]ˇ]
 9931            )}]ˇ]
 9932        "
 9933        .unindent(),
 9934    );
 9935}
 9936
 9937#[gpui::test]
 9938async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 9939    init_test(cx, |_| {});
 9940
 9941    let mut cx = EditorTestContext::new(cx).await;
 9942
 9943    let html_language = Arc::new(
 9944        Language::new(
 9945            LanguageConfig {
 9946                name: "HTML".into(),
 9947                brackets: BracketPairConfig {
 9948                    pairs: vec![
 9949                        BracketPair {
 9950                            start: "<".into(),
 9951                            end: ">".into(),
 9952                            close: true,
 9953                            ..Default::default()
 9954                        },
 9955                        BracketPair {
 9956                            start: "{".into(),
 9957                            end: "}".into(),
 9958                            close: true,
 9959                            ..Default::default()
 9960                        },
 9961                        BracketPair {
 9962                            start: "(".into(),
 9963                            end: ")".into(),
 9964                            close: true,
 9965                            ..Default::default()
 9966                        },
 9967                    ],
 9968                    ..Default::default()
 9969                },
 9970                autoclose_before: "})]>".into(),
 9971                ..Default::default()
 9972            },
 9973            Some(tree_sitter_html::LANGUAGE.into()),
 9974        )
 9975        .with_injection_query(
 9976            r#"
 9977            (script_element
 9978                (raw_text) @injection.content
 9979                (#set! injection.language "javascript"))
 9980            "#,
 9981        )
 9982        .unwrap(),
 9983    );
 9984
 9985    let javascript_language = Arc::new(Language::new(
 9986        LanguageConfig {
 9987            name: "JavaScript".into(),
 9988            brackets: BracketPairConfig {
 9989                pairs: vec![
 9990                    BracketPair {
 9991                        start: "/*".into(),
 9992                        end: " */".into(),
 9993                        close: true,
 9994                        ..Default::default()
 9995                    },
 9996                    BracketPair {
 9997                        start: "{".into(),
 9998                        end: "}".into(),
 9999                        close: true,
10000                        ..Default::default()
10001                    },
10002                    BracketPair {
10003                        start: "(".into(),
10004                        end: ")".into(),
10005                        close: true,
10006                        ..Default::default()
10007                    },
10008                ],
10009                ..Default::default()
10010            },
10011            autoclose_before: "})]>".into(),
10012            ..Default::default()
10013        },
10014        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10015    ));
10016
10017    cx.language_registry().add(html_language.clone());
10018    cx.language_registry().add(javascript_language);
10019    cx.executor().run_until_parked();
10020
10021    cx.update_buffer(|buffer, cx| {
10022        buffer.set_language(Some(html_language), cx);
10023    });
10024
10025    cx.set_state(
10026        &r#"
10027            <body>ˇ
10028                <script>
10029                    var x = 1;ˇ
10030                </script>
10031            </body>ˇ
10032        "#
10033        .unindent(),
10034    );
10035
10036    // Precondition: different languages are active at different locations.
10037    cx.update_editor(|editor, window, cx| {
10038        let snapshot = editor.snapshot(window, cx);
10039        let cursors = editor.selections.ranges::<usize>(cx);
10040        let languages = cursors
10041            .iter()
10042            .map(|c| snapshot.language_at(c.start).unwrap().name())
10043            .collect::<Vec<_>>();
10044        assert_eq!(
10045            languages,
10046            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10047        );
10048    });
10049
10050    // Angle brackets autoclose in HTML, but not JavaScript.
10051    cx.update_editor(|editor, window, cx| {
10052        editor.handle_input("<", window, cx);
10053        editor.handle_input("a", window, cx);
10054    });
10055    cx.assert_editor_state(
10056        &r#"
10057            <body><aˇ>
10058                <script>
10059                    var x = 1;<aˇ
10060                </script>
10061            </body><aˇ>
10062        "#
10063        .unindent(),
10064    );
10065
10066    // Curly braces and parens autoclose in both HTML and JavaScript.
10067    cx.update_editor(|editor, window, cx| {
10068        editor.handle_input(" b=", window, cx);
10069        editor.handle_input("{", window, cx);
10070        editor.handle_input("c", window, cx);
10071        editor.handle_input("(", window, cx);
10072    });
10073    cx.assert_editor_state(
10074        &r#"
10075            <body><a b={c(ˇ)}>
10076                <script>
10077                    var x = 1;<a b={c(ˇ)}
10078                </script>
10079            </body><a b={c(ˇ)}>
10080        "#
10081        .unindent(),
10082    );
10083
10084    // Brackets that were already autoclosed are skipped.
10085    cx.update_editor(|editor, window, cx| {
10086        editor.handle_input(")", window, cx);
10087        editor.handle_input("d", window, cx);
10088        editor.handle_input("}", window, cx);
10089    });
10090    cx.assert_editor_state(
10091        &r#"
10092            <body><a b={c()d}ˇ>
10093                <script>
10094                    var x = 1;<a b={c()d}ˇ
10095                </script>
10096            </body><a b={c()d}ˇ>
10097        "#
10098        .unindent(),
10099    );
10100    cx.update_editor(|editor, window, cx| {
10101        editor.handle_input(">", window, cx);
10102    });
10103    cx.assert_editor_state(
10104        &r#"
10105            <body><a b={c()d}>ˇ
10106                <script>
10107                    var x = 1;<a b={c()d}>ˇ
10108                </script>
10109            </body><a b={c()d}>ˇ
10110        "#
10111        .unindent(),
10112    );
10113
10114    // Reset
10115    cx.set_state(
10116        &r#"
10117            <body>ˇ
10118                <script>
10119                    var x = 1;ˇ
10120                </script>
10121            </body>ˇ
10122        "#
10123        .unindent(),
10124    );
10125
10126    cx.update_editor(|editor, window, cx| {
10127        editor.handle_input("<", window, cx);
10128    });
10129    cx.assert_editor_state(
10130        &r#"
10131            <body><ˇ>
10132                <script>
10133                    var x = 1;<ˇ
10134                </script>
10135            </body><ˇ>
10136        "#
10137        .unindent(),
10138    );
10139
10140    // When backspacing, the closing angle brackets are removed.
10141    cx.update_editor(|editor, window, cx| {
10142        editor.backspace(&Backspace, window, cx);
10143    });
10144    cx.assert_editor_state(
10145        &r#"
10146            <body>ˇ
10147                <script>
10148                    var x = 1;ˇ
10149                </script>
10150            </body>ˇ
10151        "#
10152        .unindent(),
10153    );
10154
10155    // Block comments autoclose in JavaScript, but not HTML.
10156    cx.update_editor(|editor, window, cx| {
10157        editor.handle_input("/", window, cx);
10158        editor.handle_input("*", window, cx);
10159    });
10160    cx.assert_editor_state(
10161        &r#"
10162            <body>/*ˇ
10163                <script>
10164                    var x = 1;/*ˇ */
10165                </script>
10166            </body>/*ˇ
10167        "#
10168        .unindent(),
10169    );
10170}
10171
10172#[gpui::test]
10173async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10174    init_test(cx, |_| {});
10175
10176    let mut cx = EditorTestContext::new(cx).await;
10177
10178    let rust_language = Arc::new(
10179        Language::new(
10180            LanguageConfig {
10181                name: "Rust".into(),
10182                brackets: serde_json::from_value(json!([
10183                    { "start": "{", "end": "}", "close": true, "newline": true },
10184                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10185                ]))
10186                .unwrap(),
10187                autoclose_before: "})]>".into(),
10188                ..Default::default()
10189            },
10190            Some(tree_sitter_rust::LANGUAGE.into()),
10191        )
10192        .with_override_query("(string_literal) @string")
10193        .unwrap(),
10194    );
10195
10196    cx.language_registry().add(rust_language.clone());
10197    cx.update_buffer(|buffer, cx| {
10198        buffer.set_language(Some(rust_language), cx);
10199    });
10200
10201    cx.set_state(
10202        &r#"
10203            let x = ˇ
10204        "#
10205        .unindent(),
10206    );
10207
10208    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10209    cx.update_editor(|editor, window, cx| {
10210        editor.handle_input("\"", window, cx);
10211    });
10212    cx.assert_editor_state(
10213        &r#"
10214            let x = "ˇ"
10215        "#
10216        .unindent(),
10217    );
10218
10219    // Inserting another quotation mark. The cursor moves across the existing
10220    // automatically-inserted quotation mark.
10221    cx.update_editor(|editor, window, cx| {
10222        editor.handle_input("\"", window, cx);
10223    });
10224    cx.assert_editor_state(
10225        &r#"
10226            let x = ""ˇ
10227        "#
10228        .unindent(),
10229    );
10230
10231    // Reset
10232    cx.set_state(
10233        &r#"
10234            let x = ˇ
10235        "#
10236        .unindent(),
10237    );
10238
10239    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
10240    cx.update_editor(|editor, window, cx| {
10241        editor.handle_input("\"", window, cx);
10242        editor.handle_input(" ", window, cx);
10243        editor.move_left(&Default::default(), window, cx);
10244        editor.handle_input("\\", window, cx);
10245        editor.handle_input("\"", window, cx);
10246    });
10247    cx.assert_editor_state(
10248        &r#"
10249            let x = "\"ˇ "
10250        "#
10251        .unindent(),
10252    );
10253
10254    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10255    // mark. Nothing is inserted.
10256    cx.update_editor(|editor, window, cx| {
10257        editor.move_right(&Default::default(), window, cx);
10258        editor.handle_input("\"", window, cx);
10259    });
10260    cx.assert_editor_state(
10261        &r#"
10262            let x = "\" "ˇ
10263        "#
10264        .unindent(),
10265    );
10266}
10267
10268#[gpui::test]
10269async fn test_surround_with_pair(cx: &mut TestAppContext) {
10270    init_test(cx, |_| {});
10271
10272    let language = Arc::new(Language::new(
10273        LanguageConfig {
10274            brackets: BracketPairConfig {
10275                pairs: vec![
10276                    BracketPair {
10277                        start: "{".to_string(),
10278                        end: "}".to_string(),
10279                        close: true,
10280                        surround: true,
10281                        newline: true,
10282                    },
10283                    BracketPair {
10284                        start: "/* ".to_string(),
10285                        end: "*/".to_string(),
10286                        close: true,
10287                        surround: true,
10288                        ..Default::default()
10289                    },
10290                ],
10291                ..Default::default()
10292            },
10293            ..Default::default()
10294        },
10295        Some(tree_sitter_rust::LANGUAGE.into()),
10296    ));
10297
10298    let text = r#"
10299        a
10300        b
10301        c
10302    "#
10303    .unindent();
10304
10305    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10306    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10307    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10308    editor
10309        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10310        .await;
10311
10312    editor.update_in(cx, |editor, window, cx| {
10313        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10314            s.select_display_ranges([
10315                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10316                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10317                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
10318            ])
10319        });
10320
10321        editor.handle_input("{", window, cx);
10322        editor.handle_input("{", window, cx);
10323        editor.handle_input("{", window, cx);
10324        assert_eq!(
10325            editor.text(cx),
10326            "
10327                {{{a}}}
10328                {{{b}}}
10329                {{{c}}}
10330            "
10331            .unindent()
10332        );
10333        assert_eq!(
10334            editor.selections.display_ranges(cx),
10335            [
10336                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
10337                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
10338                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
10339            ]
10340        );
10341
10342        editor.undo(&Undo, window, cx);
10343        editor.undo(&Undo, window, cx);
10344        editor.undo(&Undo, window, cx);
10345        assert_eq!(
10346            editor.text(cx),
10347            "
10348                a
10349                b
10350                c
10351            "
10352            .unindent()
10353        );
10354        assert_eq!(
10355            editor.selections.display_ranges(cx),
10356            [
10357                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10358                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10359                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10360            ]
10361        );
10362
10363        // Ensure inserting the first character of a multi-byte bracket pair
10364        // doesn't surround the selections with the bracket.
10365        editor.handle_input("/", window, cx);
10366        assert_eq!(
10367            editor.text(cx),
10368            "
10369                /
10370                /
10371                /
10372            "
10373            .unindent()
10374        );
10375        assert_eq!(
10376            editor.selections.display_ranges(cx),
10377            [
10378                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10379                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10380                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10381            ]
10382        );
10383
10384        editor.undo(&Undo, window, cx);
10385        assert_eq!(
10386            editor.text(cx),
10387            "
10388                a
10389                b
10390                c
10391            "
10392            .unindent()
10393        );
10394        assert_eq!(
10395            editor.selections.display_ranges(cx),
10396            [
10397                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10398                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10399                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10400            ]
10401        );
10402
10403        // Ensure inserting the last character of a multi-byte bracket pair
10404        // doesn't surround the selections with the bracket.
10405        editor.handle_input("*", window, cx);
10406        assert_eq!(
10407            editor.text(cx),
10408            "
10409                *
10410                *
10411                *
10412            "
10413            .unindent()
10414        );
10415        assert_eq!(
10416            editor.selections.display_ranges(cx),
10417            [
10418                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10419                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10420                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10421            ]
10422        );
10423    });
10424}
10425
10426#[gpui::test]
10427async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
10428    init_test(cx, |_| {});
10429
10430    let language = Arc::new(Language::new(
10431        LanguageConfig {
10432            brackets: BracketPairConfig {
10433                pairs: vec![BracketPair {
10434                    start: "{".to_string(),
10435                    end: "}".to_string(),
10436                    close: true,
10437                    surround: true,
10438                    newline: true,
10439                }],
10440                ..Default::default()
10441            },
10442            autoclose_before: "}".to_string(),
10443            ..Default::default()
10444        },
10445        Some(tree_sitter_rust::LANGUAGE.into()),
10446    ));
10447
10448    let text = r#"
10449        a
10450        b
10451        c
10452    "#
10453    .unindent();
10454
10455    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10456    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10457    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10458    editor
10459        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10460        .await;
10461
10462    editor.update_in(cx, |editor, window, cx| {
10463        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10464            s.select_ranges([
10465                Point::new(0, 1)..Point::new(0, 1),
10466                Point::new(1, 1)..Point::new(1, 1),
10467                Point::new(2, 1)..Point::new(2, 1),
10468            ])
10469        });
10470
10471        editor.handle_input("{", window, cx);
10472        editor.handle_input("{", window, cx);
10473        editor.handle_input("_", window, cx);
10474        assert_eq!(
10475            editor.text(cx),
10476            "
10477                a{{_}}
10478                b{{_}}
10479                c{{_}}
10480            "
10481            .unindent()
10482        );
10483        assert_eq!(
10484            editor.selections.ranges::<Point>(cx),
10485            [
10486                Point::new(0, 4)..Point::new(0, 4),
10487                Point::new(1, 4)..Point::new(1, 4),
10488                Point::new(2, 4)..Point::new(2, 4)
10489            ]
10490        );
10491
10492        editor.backspace(&Default::default(), window, cx);
10493        editor.backspace(&Default::default(), window, cx);
10494        assert_eq!(
10495            editor.text(cx),
10496            "
10497                a{}
10498                b{}
10499                c{}
10500            "
10501            .unindent()
10502        );
10503        assert_eq!(
10504            editor.selections.ranges::<Point>(cx),
10505            [
10506                Point::new(0, 2)..Point::new(0, 2),
10507                Point::new(1, 2)..Point::new(1, 2),
10508                Point::new(2, 2)..Point::new(2, 2)
10509            ]
10510        );
10511
10512        editor.delete_to_previous_word_start(&Default::default(), window, cx);
10513        assert_eq!(
10514            editor.text(cx),
10515            "
10516                a
10517                b
10518                c
10519            "
10520            .unindent()
10521        );
10522        assert_eq!(
10523            editor.selections.ranges::<Point>(cx),
10524            [
10525                Point::new(0, 1)..Point::new(0, 1),
10526                Point::new(1, 1)..Point::new(1, 1),
10527                Point::new(2, 1)..Point::new(2, 1)
10528            ]
10529        );
10530    });
10531}
10532
10533#[gpui::test]
10534async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
10535    init_test(cx, |settings| {
10536        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10537    });
10538
10539    let mut cx = EditorTestContext::new(cx).await;
10540
10541    let language = Arc::new(Language::new(
10542        LanguageConfig {
10543            brackets: BracketPairConfig {
10544                pairs: vec![
10545                    BracketPair {
10546                        start: "{".to_string(),
10547                        end: "}".to_string(),
10548                        close: true,
10549                        surround: true,
10550                        newline: true,
10551                    },
10552                    BracketPair {
10553                        start: "(".to_string(),
10554                        end: ")".to_string(),
10555                        close: true,
10556                        surround: true,
10557                        newline: true,
10558                    },
10559                    BracketPair {
10560                        start: "[".to_string(),
10561                        end: "]".to_string(),
10562                        close: false,
10563                        surround: true,
10564                        newline: true,
10565                    },
10566                ],
10567                ..Default::default()
10568            },
10569            autoclose_before: "})]".to_string(),
10570            ..Default::default()
10571        },
10572        Some(tree_sitter_rust::LANGUAGE.into()),
10573    ));
10574
10575    cx.language_registry().add(language.clone());
10576    cx.update_buffer(|buffer, cx| {
10577        buffer.set_language(Some(language), cx);
10578    });
10579
10580    cx.set_state(
10581        &"
10582            {(ˇ)}
10583            [[ˇ]]
10584            {(ˇ)}
10585        "
10586        .unindent(),
10587    );
10588
10589    cx.update_editor(|editor, window, cx| {
10590        editor.backspace(&Default::default(), window, cx);
10591        editor.backspace(&Default::default(), window, cx);
10592    });
10593
10594    cx.assert_editor_state(
10595        &"
10596            ˇ
10597            ˇ]]
10598            ˇ
10599        "
10600        .unindent(),
10601    );
10602
10603    cx.update_editor(|editor, window, cx| {
10604        editor.handle_input("{", window, cx);
10605        editor.handle_input("{", window, cx);
10606        editor.move_right(&MoveRight, window, cx);
10607        editor.move_right(&MoveRight, window, cx);
10608        editor.move_left(&MoveLeft, window, cx);
10609        editor.move_left(&MoveLeft, window, cx);
10610        editor.backspace(&Default::default(), window, cx);
10611    });
10612
10613    cx.assert_editor_state(
10614        &"
10615            {ˇ}
10616            {ˇ}]]
10617            {ˇ}
10618        "
10619        .unindent(),
10620    );
10621
10622    cx.update_editor(|editor, window, cx| {
10623        editor.backspace(&Default::default(), window, cx);
10624    });
10625
10626    cx.assert_editor_state(
10627        &"
10628            ˇ
10629            ˇ]]
10630            ˇ
10631        "
10632        .unindent(),
10633    );
10634}
10635
10636#[gpui::test]
10637async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
10638    init_test(cx, |_| {});
10639
10640    let language = Arc::new(Language::new(
10641        LanguageConfig::default(),
10642        Some(tree_sitter_rust::LANGUAGE.into()),
10643    ));
10644
10645    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
10646    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10647    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10648    editor
10649        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10650        .await;
10651
10652    editor.update_in(cx, |editor, window, cx| {
10653        editor.set_auto_replace_emoji_shortcode(true);
10654
10655        editor.handle_input("Hello ", window, cx);
10656        editor.handle_input(":wave", window, cx);
10657        assert_eq!(editor.text(cx), "Hello :wave".unindent());
10658
10659        editor.handle_input(":", window, cx);
10660        assert_eq!(editor.text(cx), "Hello 👋".unindent());
10661
10662        editor.handle_input(" :smile", window, cx);
10663        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
10664
10665        editor.handle_input(":", window, cx);
10666        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
10667
10668        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
10669        editor.handle_input(":wave", window, cx);
10670        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
10671
10672        editor.handle_input(":", window, cx);
10673        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
10674
10675        editor.handle_input(":1", window, cx);
10676        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
10677
10678        editor.handle_input(":", window, cx);
10679        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
10680
10681        // Ensure shortcode does not get replaced when it is part of a word
10682        editor.handle_input(" Test:wave", window, cx);
10683        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
10684
10685        editor.handle_input(":", window, cx);
10686        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
10687
10688        editor.set_auto_replace_emoji_shortcode(false);
10689
10690        // Ensure shortcode does not get replaced when auto replace is off
10691        editor.handle_input(" :wave", window, cx);
10692        assert_eq!(
10693            editor.text(cx),
10694            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
10695        );
10696
10697        editor.handle_input(":", window, cx);
10698        assert_eq!(
10699            editor.text(cx),
10700            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
10701        );
10702    });
10703}
10704
10705#[gpui::test]
10706async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
10707    init_test(cx, |_| {});
10708
10709    let (text, insertion_ranges) = marked_text_ranges(
10710        indoc! {"
10711            ˇ
10712        "},
10713        false,
10714    );
10715
10716    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
10717    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10718
10719    _ = editor.update_in(cx, |editor, window, cx| {
10720        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
10721
10722        editor
10723            .insert_snippet(&insertion_ranges, snippet, window, cx)
10724            .unwrap();
10725
10726        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
10727            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
10728            assert_eq!(editor.text(cx), expected_text);
10729            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
10730        }
10731
10732        assert(
10733            editor,
10734            cx,
10735            indoc! {"
10736            type «» =•
10737            "},
10738        );
10739
10740        assert!(editor.context_menu_visible(), "There should be a matches");
10741    });
10742}
10743
10744#[gpui::test]
10745async fn test_snippets(cx: &mut TestAppContext) {
10746    init_test(cx, |_| {});
10747
10748    let mut cx = EditorTestContext::new(cx).await;
10749
10750    cx.set_state(indoc! {"
10751        a.ˇ b
10752        a.ˇ b
10753        a.ˇ b
10754    "});
10755
10756    cx.update_editor(|editor, window, cx| {
10757        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
10758        let insertion_ranges = editor
10759            .selections
10760            .all(cx)
10761            .iter()
10762            .map(|s| s.range())
10763            .collect::<Vec<_>>();
10764        editor
10765            .insert_snippet(&insertion_ranges, snippet, window, cx)
10766            .unwrap();
10767    });
10768
10769    cx.assert_editor_state(indoc! {"
10770        a.f(«oneˇ», two, «threeˇ») b
10771        a.f(«oneˇ», two, «threeˇ») b
10772        a.f(«oneˇ», two, «threeˇ») b
10773    "});
10774
10775    // Can't move earlier than the first tab stop
10776    cx.update_editor(|editor, window, cx| {
10777        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10778    });
10779    cx.assert_editor_state(indoc! {"
10780        a.f(«oneˇ», two, «threeˇ») b
10781        a.f(«oneˇ», two, «threeˇ») b
10782        a.f(«oneˇ», two, «threeˇ») b
10783    "});
10784
10785    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10786    cx.assert_editor_state(indoc! {"
10787        a.f(one, «twoˇ», three) b
10788        a.f(one, «twoˇ», three) b
10789        a.f(one, «twoˇ», three) b
10790    "});
10791
10792    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
10793    cx.assert_editor_state(indoc! {"
10794        a.f(«oneˇ», two, «threeˇ») b
10795        a.f(«oneˇ», two, «threeˇ») b
10796        a.f(«oneˇ», two, «threeˇ») b
10797    "});
10798
10799    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10800    cx.assert_editor_state(indoc! {"
10801        a.f(one, «twoˇ», three) b
10802        a.f(one, «twoˇ», three) b
10803        a.f(one, «twoˇ», three) b
10804    "});
10805    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10806    cx.assert_editor_state(indoc! {"
10807        a.f(one, two, three)ˇ b
10808        a.f(one, two, three)ˇ b
10809        a.f(one, two, three)ˇ b
10810    "});
10811
10812    // As soon as the last tab stop is reached, snippet state is gone
10813    cx.update_editor(|editor, window, cx| {
10814        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10815    });
10816    cx.assert_editor_state(indoc! {"
10817        a.f(one, two, three)ˇ b
10818        a.f(one, two, three)ˇ b
10819        a.f(one, two, three)ˇ b
10820    "});
10821}
10822
10823#[gpui::test]
10824async fn test_snippet_indentation(cx: &mut TestAppContext) {
10825    init_test(cx, |_| {});
10826
10827    let mut cx = EditorTestContext::new(cx).await;
10828
10829    cx.update_editor(|editor, window, cx| {
10830        let snippet = Snippet::parse(indoc! {"
10831            /*
10832             * Multiline comment with leading indentation
10833             *
10834             * $1
10835             */
10836            $0"})
10837        .unwrap();
10838        let insertion_ranges = editor
10839            .selections
10840            .all(cx)
10841            .iter()
10842            .map(|s| s.range())
10843            .collect::<Vec<_>>();
10844        editor
10845            .insert_snippet(&insertion_ranges, snippet, window, cx)
10846            .unwrap();
10847    });
10848
10849    cx.assert_editor_state(indoc! {"
10850        /*
10851         * Multiline comment with leading indentation
10852         *
10853         * ˇ
10854         */
10855    "});
10856
10857    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10858    cx.assert_editor_state(indoc! {"
10859        /*
10860         * Multiline comment with leading indentation
10861         *
10862         *•
10863         */
10864        ˇ"});
10865}
10866
10867#[gpui::test]
10868async fn test_document_format_during_save(cx: &mut TestAppContext) {
10869    init_test(cx, |_| {});
10870
10871    let fs = FakeFs::new(cx.executor());
10872    fs.insert_file(path!("/file.rs"), Default::default()).await;
10873
10874    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
10875
10876    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10877    language_registry.add(rust_lang());
10878    let mut fake_servers = language_registry.register_fake_lsp(
10879        "Rust",
10880        FakeLspAdapter {
10881            capabilities: lsp::ServerCapabilities {
10882                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10883                ..Default::default()
10884            },
10885            ..Default::default()
10886        },
10887    );
10888
10889    let buffer = project
10890        .update(cx, |project, cx| {
10891            project.open_local_buffer(path!("/file.rs"), cx)
10892        })
10893        .await
10894        .unwrap();
10895
10896    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10897    let (editor, cx) = cx.add_window_view(|window, cx| {
10898        build_editor_with_project(project.clone(), buffer, window, cx)
10899    });
10900    editor.update_in(cx, |editor, window, cx| {
10901        editor.set_text("one\ntwo\nthree\n", window, cx)
10902    });
10903    assert!(cx.read(|cx| editor.is_dirty(cx)));
10904
10905    cx.executor().start_waiting();
10906    let fake_server = fake_servers.next().await.unwrap();
10907
10908    {
10909        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10910            move |params, _| async move {
10911                assert_eq!(
10912                    params.text_document.uri,
10913                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10914                );
10915                assert_eq!(params.options.tab_size, 4);
10916                Ok(Some(vec![lsp::TextEdit::new(
10917                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10918                    ", ".to_string(),
10919                )]))
10920            },
10921        );
10922        let save = editor
10923            .update_in(cx, |editor, window, cx| {
10924                editor.save(
10925                    SaveOptions {
10926                        format: true,
10927                        autosave: false,
10928                    },
10929                    project.clone(),
10930                    window,
10931                    cx,
10932                )
10933            })
10934            .unwrap();
10935        cx.executor().start_waiting();
10936        save.await;
10937
10938        assert_eq!(
10939            editor.update(cx, |editor, cx| editor.text(cx)),
10940            "one, two\nthree\n"
10941        );
10942        assert!(!cx.read(|cx| editor.is_dirty(cx)));
10943    }
10944
10945    {
10946        editor.update_in(cx, |editor, window, cx| {
10947            editor.set_text("one\ntwo\nthree\n", window, cx)
10948        });
10949        assert!(cx.read(|cx| editor.is_dirty(cx)));
10950
10951        // Ensure we can still save even if formatting hangs.
10952        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10953            move |params, _| async move {
10954                assert_eq!(
10955                    params.text_document.uri,
10956                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10957                );
10958                futures::future::pending::<()>().await;
10959                unreachable!()
10960            },
10961        );
10962        let save = editor
10963            .update_in(cx, |editor, window, cx| {
10964                editor.save(
10965                    SaveOptions {
10966                        format: true,
10967                        autosave: false,
10968                    },
10969                    project.clone(),
10970                    window,
10971                    cx,
10972                )
10973            })
10974            .unwrap();
10975        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10976        cx.executor().start_waiting();
10977        save.await;
10978        assert_eq!(
10979            editor.update(cx, |editor, cx| editor.text(cx)),
10980            "one\ntwo\nthree\n"
10981        );
10982    }
10983
10984    // Set rust language override and assert overridden tabsize is sent to language server
10985    update_test_language_settings(cx, |settings| {
10986        settings.languages.0.insert(
10987            "Rust".into(),
10988            LanguageSettingsContent {
10989                tab_size: NonZeroU32::new(8),
10990                ..Default::default()
10991            },
10992        );
10993    });
10994
10995    {
10996        editor.update_in(cx, |editor, window, cx| {
10997            editor.set_text("somehting_new\n", window, cx)
10998        });
10999        assert!(cx.read(|cx| editor.is_dirty(cx)));
11000        let _formatting_request_signal = fake_server
11001            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11002                assert_eq!(
11003                    params.text_document.uri,
11004                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11005                );
11006                assert_eq!(params.options.tab_size, 8);
11007                Ok(Some(vec![]))
11008            });
11009        let save = editor
11010            .update_in(cx, |editor, window, cx| {
11011                editor.save(
11012                    SaveOptions {
11013                        format: true,
11014                        autosave: false,
11015                    },
11016                    project.clone(),
11017                    window,
11018                    cx,
11019                )
11020            })
11021            .unwrap();
11022        cx.executor().start_waiting();
11023        save.await;
11024    }
11025}
11026
11027#[gpui::test]
11028async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11029    init_test(cx, |settings| {
11030        settings.defaults.ensure_final_newline_on_save = Some(false);
11031    });
11032
11033    let fs = FakeFs::new(cx.executor());
11034    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11035
11036    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11037
11038    let buffer = project
11039        .update(cx, |project, cx| {
11040            project.open_local_buffer(path!("/file.txt"), cx)
11041        })
11042        .await
11043        .unwrap();
11044
11045    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11046    let (editor, cx) = cx.add_window_view(|window, cx| {
11047        build_editor_with_project(project.clone(), buffer, window, cx)
11048    });
11049    editor.update_in(cx, |editor, window, cx| {
11050        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11051            s.select_ranges([0..0])
11052        });
11053    });
11054    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11055
11056    editor.update_in(cx, |editor, window, cx| {
11057        editor.handle_input("\n", window, cx)
11058    });
11059    cx.run_until_parked();
11060    save(&editor, &project, cx).await;
11061    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11062
11063    editor.update_in(cx, |editor, window, cx| {
11064        editor.undo(&Default::default(), window, cx);
11065    });
11066    save(&editor, &project, cx).await;
11067    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11068
11069    editor.update_in(cx, |editor, window, cx| {
11070        editor.redo(&Default::default(), window, cx);
11071    });
11072    cx.run_until_parked();
11073    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11074
11075    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11076        let save = editor
11077            .update_in(cx, |editor, window, cx| {
11078                editor.save(
11079                    SaveOptions {
11080                        format: true,
11081                        autosave: false,
11082                    },
11083                    project.clone(),
11084                    window,
11085                    cx,
11086                )
11087            })
11088            .unwrap();
11089        cx.executor().start_waiting();
11090        save.await;
11091        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11092    }
11093}
11094
11095#[gpui::test]
11096async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
11097    init_test(cx, |_| {});
11098
11099    let cols = 4;
11100    let rows = 10;
11101    let sample_text_1 = sample_text(rows, cols, 'a');
11102    assert_eq!(
11103        sample_text_1,
11104        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11105    );
11106    let sample_text_2 = sample_text(rows, cols, 'l');
11107    assert_eq!(
11108        sample_text_2,
11109        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11110    );
11111    let sample_text_3 = sample_text(rows, cols, 'v');
11112    assert_eq!(
11113        sample_text_3,
11114        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11115    );
11116
11117    let fs = FakeFs::new(cx.executor());
11118    fs.insert_tree(
11119        path!("/a"),
11120        json!({
11121            "main.rs": sample_text_1,
11122            "other.rs": sample_text_2,
11123            "lib.rs": sample_text_3,
11124        }),
11125    )
11126    .await;
11127
11128    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11129    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11130    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11131
11132    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11133    language_registry.add(rust_lang());
11134    let mut fake_servers = language_registry.register_fake_lsp(
11135        "Rust",
11136        FakeLspAdapter {
11137            capabilities: lsp::ServerCapabilities {
11138                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11139                ..Default::default()
11140            },
11141            ..Default::default()
11142        },
11143    );
11144
11145    let worktree = project.update(cx, |project, cx| {
11146        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
11147        assert_eq!(worktrees.len(), 1);
11148        worktrees.pop().unwrap()
11149    });
11150    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11151
11152    let buffer_1 = project
11153        .update(cx, |project, cx| {
11154            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
11155        })
11156        .await
11157        .unwrap();
11158    let buffer_2 = project
11159        .update(cx, |project, cx| {
11160            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
11161        })
11162        .await
11163        .unwrap();
11164    let buffer_3 = project
11165        .update(cx, |project, cx| {
11166            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
11167        })
11168        .await
11169        .unwrap();
11170
11171    let multi_buffer = cx.new(|cx| {
11172        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11173        multi_buffer.push_excerpts(
11174            buffer_1.clone(),
11175            [
11176                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11177                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11178                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11179            ],
11180            cx,
11181        );
11182        multi_buffer.push_excerpts(
11183            buffer_2.clone(),
11184            [
11185                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11186                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11187                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11188            ],
11189            cx,
11190        );
11191        multi_buffer.push_excerpts(
11192            buffer_3.clone(),
11193            [
11194                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11195                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11196                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11197            ],
11198            cx,
11199        );
11200        multi_buffer
11201    });
11202    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
11203        Editor::new(
11204            EditorMode::full(),
11205            multi_buffer,
11206            Some(project.clone()),
11207            window,
11208            cx,
11209        )
11210    });
11211
11212    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11213        editor.change_selections(
11214            SelectionEffects::scroll(Autoscroll::Next),
11215            window,
11216            cx,
11217            |s| s.select_ranges(Some(1..2)),
11218        );
11219        editor.insert("|one|two|three|", window, cx);
11220    });
11221    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11222    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11223        editor.change_selections(
11224            SelectionEffects::scroll(Autoscroll::Next),
11225            window,
11226            cx,
11227            |s| s.select_ranges(Some(60..70)),
11228        );
11229        editor.insert("|four|five|six|", window, cx);
11230    });
11231    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11232
11233    // First two buffers should be edited, but not the third one.
11234    assert_eq!(
11235        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11236        "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}",
11237    );
11238    buffer_1.update(cx, |buffer, _| {
11239        assert!(buffer.is_dirty());
11240        assert_eq!(
11241            buffer.text(),
11242            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
11243        )
11244    });
11245    buffer_2.update(cx, |buffer, _| {
11246        assert!(buffer.is_dirty());
11247        assert_eq!(
11248            buffer.text(),
11249            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
11250        )
11251    });
11252    buffer_3.update(cx, |buffer, _| {
11253        assert!(!buffer.is_dirty());
11254        assert_eq!(buffer.text(), sample_text_3,)
11255    });
11256    cx.executor().run_until_parked();
11257
11258    cx.executor().start_waiting();
11259    let save = multi_buffer_editor
11260        .update_in(cx, |editor, window, cx| {
11261            editor.save(
11262                SaveOptions {
11263                    format: true,
11264                    autosave: false,
11265                },
11266                project.clone(),
11267                window,
11268                cx,
11269            )
11270        })
11271        .unwrap();
11272
11273    let fake_server = fake_servers.next().await.unwrap();
11274    fake_server
11275        .server
11276        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11277            Ok(Some(vec![lsp::TextEdit::new(
11278                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11279                format!("[{} formatted]", params.text_document.uri),
11280            )]))
11281        })
11282        .detach();
11283    save.await;
11284
11285    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11286    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11287    assert_eq!(
11288        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11289        uri!(
11290            "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}"
11291        ),
11292    );
11293    buffer_1.update(cx, |buffer, _| {
11294        assert!(!buffer.is_dirty());
11295        assert_eq!(
11296            buffer.text(),
11297            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11298        )
11299    });
11300    buffer_2.update(cx, |buffer, _| {
11301        assert!(!buffer.is_dirty());
11302        assert_eq!(
11303            buffer.text(),
11304            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11305        )
11306    });
11307    buffer_3.update(cx, |buffer, _| {
11308        assert!(!buffer.is_dirty());
11309        assert_eq!(buffer.text(), sample_text_3,)
11310    });
11311}
11312
11313#[gpui::test]
11314async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
11315    init_test(cx, |_| {});
11316
11317    let fs = FakeFs::new(cx.executor());
11318    fs.insert_tree(
11319        path!("/dir"),
11320        json!({
11321            "file1.rs": "fn main() { println!(\"hello\"); }",
11322            "file2.rs": "fn test() { println!(\"test\"); }",
11323            "file3.rs": "fn other() { println!(\"other\"); }\n",
11324        }),
11325    )
11326    .await;
11327
11328    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
11329    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11330    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11331
11332    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11333    language_registry.add(rust_lang());
11334
11335    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
11336    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11337
11338    // Open three buffers
11339    let buffer_1 = project
11340        .update(cx, |project, cx| {
11341            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
11342        })
11343        .await
11344        .unwrap();
11345    let buffer_2 = project
11346        .update(cx, |project, cx| {
11347            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
11348        })
11349        .await
11350        .unwrap();
11351    let buffer_3 = project
11352        .update(cx, |project, cx| {
11353            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
11354        })
11355        .await
11356        .unwrap();
11357
11358    // Create a multi-buffer with all three buffers
11359    let multi_buffer = cx.new(|cx| {
11360        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11361        multi_buffer.push_excerpts(
11362            buffer_1.clone(),
11363            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11364            cx,
11365        );
11366        multi_buffer.push_excerpts(
11367            buffer_2.clone(),
11368            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11369            cx,
11370        );
11371        multi_buffer.push_excerpts(
11372            buffer_3.clone(),
11373            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11374            cx,
11375        );
11376        multi_buffer
11377    });
11378
11379    let editor = cx.new_window_entity(|window, cx| {
11380        Editor::new(
11381            EditorMode::full(),
11382            multi_buffer,
11383            Some(project.clone()),
11384            window,
11385            cx,
11386        )
11387    });
11388
11389    // Edit only the first buffer
11390    editor.update_in(cx, |editor, window, cx| {
11391        editor.change_selections(
11392            SelectionEffects::scroll(Autoscroll::Next),
11393            window,
11394            cx,
11395            |s| s.select_ranges(Some(10..10)),
11396        );
11397        editor.insert("// edited", window, cx);
11398    });
11399
11400    // Verify that only buffer 1 is dirty
11401    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
11402    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11403    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11404
11405    // Get write counts after file creation (files were created with initial content)
11406    // We expect each file to have been written once during creation
11407    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
11408    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
11409    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
11410
11411    // Perform autosave
11412    let save_task = editor.update_in(cx, |editor, window, cx| {
11413        editor.save(
11414            SaveOptions {
11415                format: true,
11416                autosave: true,
11417            },
11418            project.clone(),
11419            window,
11420            cx,
11421        )
11422    });
11423    save_task.await.unwrap();
11424
11425    // Only the dirty buffer should have been saved
11426    assert_eq!(
11427        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11428        1,
11429        "Buffer 1 was dirty, so it should have been written once during autosave"
11430    );
11431    assert_eq!(
11432        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11433        0,
11434        "Buffer 2 was clean, so it should not have been written during autosave"
11435    );
11436    assert_eq!(
11437        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11438        0,
11439        "Buffer 3 was clean, so it should not have been written during autosave"
11440    );
11441
11442    // Verify buffer states after autosave
11443    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11444    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11445    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11446
11447    // Now perform a manual save (format = true)
11448    let save_task = editor.update_in(cx, |editor, window, cx| {
11449        editor.save(
11450            SaveOptions {
11451                format: true,
11452                autosave: false,
11453            },
11454            project.clone(),
11455            window,
11456            cx,
11457        )
11458    });
11459    save_task.await.unwrap();
11460
11461    // During manual save, clean buffers don't get written to disk
11462    // They just get did_save called for language server notifications
11463    assert_eq!(
11464        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11465        1,
11466        "Buffer 1 should only have been written once total (during autosave, not manual save)"
11467    );
11468    assert_eq!(
11469        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11470        0,
11471        "Buffer 2 should not have been written at all"
11472    );
11473    assert_eq!(
11474        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11475        0,
11476        "Buffer 3 should not have been written at all"
11477    );
11478}
11479
11480async fn setup_range_format_test(
11481    cx: &mut TestAppContext,
11482) -> (
11483    Entity<Project>,
11484    Entity<Editor>,
11485    &mut gpui::VisualTestContext,
11486    lsp::FakeLanguageServer,
11487) {
11488    init_test(cx, |_| {});
11489
11490    let fs = FakeFs::new(cx.executor());
11491    fs.insert_file(path!("/file.rs"), Default::default()).await;
11492
11493    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11494
11495    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11496    language_registry.add(rust_lang());
11497    let mut fake_servers = language_registry.register_fake_lsp(
11498        "Rust",
11499        FakeLspAdapter {
11500            capabilities: lsp::ServerCapabilities {
11501                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
11502                ..lsp::ServerCapabilities::default()
11503            },
11504            ..FakeLspAdapter::default()
11505        },
11506    );
11507
11508    let buffer = project
11509        .update(cx, |project, cx| {
11510            project.open_local_buffer(path!("/file.rs"), cx)
11511        })
11512        .await
11513        .unwrap();
11514
11515    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11516    let (editor, cx) = cx.add_window_view(|window, cx| {
11517        build_editor_with_project(project.clone(), buffer, window, cx)
11518    });
11519
11520    cx.executor().start_waiting();
11521    let fake_server = fake_servers.next().await.unwrap();
11522
11523    (project, editor, cx, fake_server)
11524}
11525
11526#[gpui::test]
11527async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
11528    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11529
11530    editor.update_in(cx, |editor, window, cx| {
11531        editor.set_text("one\ntwo\nthree\n", window, cx)
11532    });
11533    assert!(cx.read(|cx| editor.is_dirty(cx)));
11534
11535    let save = editor
11536        .update_in(cx, |editor, window, cx| {
11537            editor.save(
11538                SaveOptions {
11539                    format: true,
11540                    autosave: false,
11541                },
11542                project.clone(),
11543                window,
11544                cx,
11545            )
11546        })
11547        .unwrap();
11548    fake_server
11549        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11550            assert_eq!(
11551                params.text_document.uri,
11552                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11553            );
11554            assert_eq!(params.options.tab_size, 4);
11555            Ok(Some(vec![lsp::TextEdit::new(
11556                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11557                ", ".to_string(),
11558            )]))
11559        })
11560        .next()
11561        .await;
11562    cx.executor().start_waiting();
11563    save.await;
11564    assert_eq!(
11565        editor.update(cx, |editor, cx| editor.text(cx)),
11566        "one, two\nthree\n"
11567    );
11568    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11569}
11570
11571#[gpui::test]
11572async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
11573    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11574
11575    editor.update_in(cx, |editor, window, cx| {
11576        editor.set_text("one\ntwo\nthree\n", window, cx)
11577    });
11578    assert!(cx.read(|cx| editor.is_dirty(cx)));
11579
11580    // Test that save still works when formatting hangs
11581    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
11582        move |params, _| async move {
11583            assert_eq!(
11584                params.text_document.uri,
11585                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11586            );
11587            futures::future::pending::<()>().await;
11588            unreachable!()
11589        },
11590    );
11591    let save = editor
11592        .update_in(cx, |editor, window, cx| {
11593            editor.save(
11594                SaveOptions {
11595                    format: true,
11596                    autosave: false,
11597                },
11598                project.clone(),
11599                window,
11600                cx,
11601            )
11602        })
11603        .unwrap();
11604    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11605    cx.executor().start_waiting();
11606    save.await;
11607    assert_eq!(
11608        editor.update(cx, |editor, cx| editor.text(cx)),
11609        "one\ntwo\nthree\n"
11610    );
11611    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11612}
11613
11614#[gpui::test]
11615async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
11616    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11617
11618    // Buffer starts clean, no formatting should be requested
11619    let save = editor
11620        .update_in(cx, |editor, window, cx| {
11621            editor.save(
11622                SaveOptions {
11623                    format: false,
11624                    autosave: false,
11625                },
11626                project.clone(),
11627                window,
11628                cx,
11629            )
11630        })
11631        .unwrap();
11632    let _pending_format_request = fake_server
11633        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
11634            panic!("Should not be invoked");
11635        })
11636        .next();
11637    cx.executor().start_waiting();
11638    save.await;
11639    cx.run_until_parked();
11640}
11641
11642#[gpui::test]
11643async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
11644    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11645
11646    // Set Rust language override and assert overridden tabsize is sent to language server
11647    update_test_language_settings(cx, |settings| {
11648        settings.languages.0.insert(
11649            "Rust".into(),
11650            LanguageSettingsContent {
11651                tab_size: NonZeroU32::new(8),
11652                ..Default::default()
11653            },
11654        );
11655    });
11656
11657    editor.update_in(cx, |editor, window, cx| {
11658        editor.set_text("something_new\n", window, cx)
11659    });
11660    assert!(cx.read(|cx| editor.is_dirty(cx)));
11661    let save = editor
11662        .update_in(cx, |editor, window, cx| {
11663            editor.save(
11664                SaveOptions {
11665                    format: true,
11666                    autosave: false,
11667                },
11668                project.clone(),
11669                window,
11670                cx,
11671            )
11672        })
11673        .unwrap();
11674    fake_server
11675        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11676            assert_eq!(
11677                params.text_document.uri,
11678                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11679            );
11680            assert_eq!(params.options.tab_size, 8);
11681            Ok(Some(Vec::new()))
11682        })
11683        .next()
11684        .await;
11685    save.await;
11686}
11687
11688#[gpui::test]
11689async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
11690    init_test(cx, |settings| {
11691        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
11692            Formatter::LanguageServer { name: None },
11693        )))
11694    });
11695
11696    let fs = FakeFs::new(cx.executor());
11697    fs.insert_file(path!("/file.rs"), Default::default()).await;
11698
11699    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11700
11701    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11702    language_registry.add(Arc::new(Language::new(
11703        LanguageConfig {
11704            name: "Rust".into(),
11705            matcher: LanguageMatcher {
11706                path_suffixes: vec!["rs".to_string()],
11707                ..Default::default()
11708            },
11709            ..LanguageConfig::default()
11710        },
11711        Some(tree_sitter_rust::LANGUAGE.into()),
11712    )));
11713    update_test_language_settings(cx, |settings| {
11714        // Enable Prettier formatting for the same buffer, and ensure
11715        // LSP is called instead of Prettier.
11716        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
11717    });
11718    let mut fake_servers = language_registry.register_fake_lsp(
11719        "Rust",
11720        FakeLspAdapter {
11721            capabilities: lsp::ServerCapabilities {
11722                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11723                ..Default::default()
11724            },
11725            ..Default::default()
11726        },
11727    );
11728
11729    let buffer = project
11730        .update(cx, |project, cx| {
11731            project.open_local_buffer(path!("/file.rs"), cx)
11732        })
11733        .await
11734        .unwrap();
11735
11736    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11737    let (editor, cx) = cx.add_window_view(|window, cx| {
11738        build_editor_with_project(project.clone(), buffer, window, cx)
11739    });
11740    editor.update_in(cx, |editor, window, cx| {
11741        editor.set_text("one\ntwo\nthree\n", window, cx)
11742    });
11743
11744    cx.executor().start_waiting();
11745    let fake_server = fake_servers.next().await.unwrap();
11746
11747    let format = editor
11748        .update_in(cx, |editor, window, cx| {
11749            editor.perform_format(
11750                project.clone(),
11751                FormatTrigger::Manual,
11752                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11753                window,
11754                cx,
11755            )
11756        })
11757        .unwrap();
11758    fake_server
11759        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11760            assert_eq!(
11761                params.text_document.uri,
11762                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11763            );
11764            assert_eq!(params.options.tab_size, 4);
11765            Ok(Some(vec![lsp::TextEdit::new(
11766                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11767                ", ".to_string(),
11768            )]))
11769        })
11770        .next()
11771        .await;
11772    cx.executor().start_waiting();
11773    format.await;
11774    assert_eq!(
11775        editor.update(cx, |editor, cx| editor.text(cx)),
11776        "one, two\nthree\n"
11777    );
11778
11779    editor.update_in(cx, |editor, window, cx| {
11780        editor.set_text("one\ntwo\nthree\n", window, cx)
11781    });
11782    // Ensure we don't lock if formatting hangs.
11783    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11784        move |params, _| async move {
11785            assert_eq!(
11786                params.text_document.uri,
11787                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11788            );
11789            futures::future::pending::<()>().await;
11790            unreachable!()
11791        },
11792    );
11793    let format = editor
11794        .update_in(cx, |editor, window, cx| {
11795            editor.perform_format(
11796                project,
11797                FormatTrigger::Manual,
11798                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11799                window,
11800                cx,
11801            )
11802        })
11803        .unwrap();
11804    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11805    cx.executor().start_waiting();
11806    format.await;
11807    assert_eq!(
11808        editor.update(cx, |editor, cx| editor.text(cx)),
11809        "one\ntwo\nthree\n"
11810    );
11811}
11812
11813#[gpui::test]
11814async fn test_multiple_formatters(cx: &mut TestAppContext) {
11815    init_test(cx, |settings| {
11816        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
11817        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
11818            Formatter::LanguageServer { name: None },
11819            Formatter::CodeActions(
11820                [
11821                    ("code-action-1".into(), true),
11822                    ("code-action-2".into(), true),
11823                ]
11824                .into_iter()
11825                .collect(),
11826            ),
11827        ])))
11828    });
11829
11830    let fs = FakeFs::new(cx.executor());
11831    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
11832        .await;
11833
11834    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11835    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11836    language_registry.add(rust_lang());
11837
11838    let mut fake_servers = language_registry.register_fake_lsp(
11839        "Rust",
11840        FakeLspAdapter {
11841            capabilities: lsp::ServerCapabilities {
11842                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11843                execute_command_provider: Some(lsp::ExecuteCommandOptions {
11844                    commands: vec!["the-command-for-code-action-1".into()],
11845                    ..Default::default()
11846                }),
11847                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
11848                ..Default::default()
11849            },
11850            ..Default::default()
11851        },
11852    );
11853
11854    let buffer = project
11855        .update(cx, |project, cx| {
11856            project.open_local_buffer(path!("/file.rs"), cx)
11857        })
11858        .await
11859        .unwrap();
11860
11861    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11862    let (editor, cx) = cx.add_window_view(|window, cx| {
11863        build_editor_with_project(project.clone(), buffer, window, cx)
11864    });
11865
11866    cx.executor().start_waiting();
11867
11868    let fake_server = fake_servers.next().await.unwrap();
11869    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11870        move |_params, _| async move {
11871            Ok(Some(vec![lsp::TextEdit::new(
11872                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11873                "applied-formatting\n".to_string(),
11874            )]))
11875        },
11876    );
11877    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
11878        move |params, _| async move {
11879            assert_eq!(
11880                params.context.only,
11881                Some(vec!["code-action-1".into(), "code-action-2".into()])
11882            );
11883            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
11884            Ok(Some(vec![
11885                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
11886                    kind: Some("code-action-1".into()),
11887                    edit: Some(lsp::WorkspaceEdit::new(
11888                        [(
11889                            uri.clone(),
11890                            vec![lsp::TextEdit::new(
11891                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11892                                "applied-code-action-1-edit\n".to_string(),
11893                            )],
11894                        )]
11895                        .into_iter()
11896                        .collect(),
11897                    )),
11898                    command: Some(lsp::Command {
11899                        command: "the-command-for-code-action-1".into(),
11900                        ..Default::default()
11901                    }),
11902                    ..Default::default()
11903                }),
11904                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
11905                    kind: Some("code-action-2".into()),
11906                    edit: Some(lsp::WorkspaceEdit::new(
11907                        [(
11908                            uri,
11909                            vec![lsp::TextEdit::new(
11910                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11911                                "applied-code-action-2-edit\n".to_string(),
11912                            )],
11913                        )]
11914                        .into_iter()
11915                        .collect(),
11916                    )),
11917                    ..Default::default()
11918                }),
11919            ]))
11920        },
11921    );
11922
11923    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
11924        move |params, _| async move { Ok(params) }
11925    });
11926
11927    let command_lock = Arc::new(futures::lock::Mutex::new(()));
11928    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
11929        let fake = fake_server.clone();
11930        let lock = command_lock.clone();
11931        move |params, _| {
11932            assert_eq!(params.command, "the-command-for-code-action-1");
11933            let fake = fake.clone();
11934            let lock = lock.clone();
11935            async move {
11936                lock.lock().await;
11937                fake.server
11938                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
11939                        label: None,
11940                        edit: lsp::WorkspaceEdit {
11941                            changes: Some(
11942                                [(
11943                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
11944                                    vec![lsp::TextEdit {
11945                                        range: lsp::Range::new(
11946                                            lsp::Position::new(0, 0),
11947                                            lsp::Position::new(0, 0),
11948                                        ),
11949                                        new_text: "applied-code-action-1-command\n".into(),
11950                                    }],
11951                                )]
11952                                .into_iter()
11953                                .collect(),
11954                            ),
11955                            ..Default::default()
11956                        },
11957                    })
11958                    .await
11959                    .into_response()
11960                    .unwrap();
11961                Ok(Some(json!(null)))
11962            }
11963        }
11964    });
11965
11966    cx.executor().start_waiting();
11967    editor
11968        .update_in(cx, |editor, window, cx| {
11969            editor.perform_format(
11970                project.clone(),
11971                FormatTrigger::Manual,
11972                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11973                window,
11974                cx,
11975            )
11976        })
11977        .unwrap()
11978        .await;
11979    editor.update(cx, |editor, cx| {
11980        assert_eq!(
11981            editor.text(cx),
11982            r#"
11983                applied-code-action-2-edit
11984                applied-code-action-1-command
11985                applied-code-action-1-edit
11986                applied-formatting
11987                one
11988                two
11989                three
11990            "#
11991            .unindent()
11992        );
11993    });
11994
11995    editor.update_in(cx, |editor, window, cx| {
11996        editor.undo(&Default::default(), window, cx);
11997        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
11998    });
11999
12000    // Perform a manual edit while waiting for an LSP command
12001    // that's being run as part of a formatting code action.
12002    let lock_guard = command_lock.lock().await;
12003    let format = editor
12004        .update_in(cx, |editor, window, cx| {
12005            editor.perform_format(
12006                project.clone(),
12007                FormatTrigger::Manual,
12008                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12009                window,
12010                cx,
12011            )
12012        })
12013        .unwrap();
12014    cx.run_until_parked();
12015    editor.update(cx, |editor, cx| {
12016        assert_eq!(
12017            editor.text(cx),
12018            r#"
12019                applied-code-action-1-edit
12020                applied-formatting
12021                one
12022                two
12023                three
12024            "#
12025            .unindent()
12026        );
12027
12028        editor.buffer.update(cx, |buffer, cx| {
12029            let ix = buffer.len(cx);
12030            buffer.edit([(ix..ix, "edited\n")], None, cx);
12031        });
12032    });
12033
12034    // Allow the LSP command to proceed. Because the buffer was edited,
12035    // the second code action will not be run.
12036    drop(lock_guard);
12037    format.await;
12038    editor.update_in(cx, |editor, window, cx| {
12039        assert_eq!(
12040            editor.text(cx),
12041            r#"
12042                applied-code-action-1-command
12043                applied-code-action-1-edit
12044                applied-formatting
12045                one
12046                two
12047                three
12048                edited
12049            "#
12050            .unindent()
12051        );
12052
12053        // The manual edit is undone first, because it is the last thing the user did
12054        // (even though the command completed afterwards).
12055        editor.undo(&Default::default(), window, cx);
12056        assert_eq!(
12057            editor.text(cx),
12058            r#"
12059                applied-code-action-1-command
12060                applied-code-action-1-edit
12061                applied-formatting
12062                one
12063                two
12064                three
12065            "#
12066            .unindent()
12067        );
12068
12069        // All the formatting (including the command, which completed after the manual edit)
12070        // is undone together.
12071        editor.undo(&Default::default(), window, cx);
12072        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12073    });
12074}
12075
12076#[gpui::test]
12077async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12078    init_test(cx, |settings| {
12079        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
12080            Formatter::LanguageServer { name: None },
12081        ])))
12082    });
12083
12084    let fs = FakeFs::new(cx.executor());
12085    fs.insert_file(path!("/file.ts"), Default::default()).await;
12086
12087    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12088
12089    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12090    language_registry.add(Arc::new(Language::new(
12091        LanguageConfig {
12092            name: "TypeScript".into(),
12093            matcher: LanguageMatcher {
12094                path_suffixes: vec!["ts".to_string()],
12095                ..Default::default()
12096            },
12097            ..LanguageConfig::default()
12098        },
12099        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12100    )));
12101    update_test_language_settings(cx, |settings| {
12102        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12103    });
12104    let mut fake_servers = language_registry.register_fake_lsp(
12105        "TypeScript",
12106        FakeLspAdapter {
12107            capabilities: lsp::ServerCapabilities {
12108                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12109                ..Default::default()
12110            },
12111            ..Default::default()
12112        },
12113    );
12114
12115    let buffer = project
12116        .update(cx, |project, cx| {
12117            project.open_local_buffer(path!("/file.ts"), cx)
12118        })
12119        .await
12120        .unwrap();
12121
12122    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12123    let (editor, cx) = cx.add_window_view(|window, cx| {
12124        build_editor_with_project(project.clone(), buffer, window, cx)
12125    });
12126    editor.update_in(cx, |editor, window, cx| {
12127        editor.set_text(
12128            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12129            window,
12130            cx,
12131        )
12132    });
12133
12134    cx.executor().start_waiting();
12135    let fake_server = fake_servers.next().await.unwrap();
12136
12137    let format = editor
12138        .update_in(cx, |editor, window, cx| {
12139            editor.perform_code_action_kind(
12140                project.clone(),
12141                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12142                window,
12143                cx,
12144            )
12145        })
12146        .unwrap();
12147    fake_server
12148        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12149            assert_eq!(
12150                params.text_document.uri,
12151                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12152            );
12153            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12154                lsp::CodeAction {
12155                    title: "Organize Imports".to_string(),
12156                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12157                    edit: Some(lsp::WorkspaceEdit {
12158                        changes: Some(
12159                            [(
12160                                params.text_document.uri.clone(),
12161                                vec![lsp::TextEdit::new(
12162                                    lsp::Range::new(
12163                                        lsp::Position::new(1, 0),
12164                                        lsp::Position::new(2, 0),
12165                                    ),
12166                                    "".to_string(),
12167                                )],
12168                            )]
12169                            .into_iter()
12170                            .collect(),
12171                        ),
12172                        ..Default::default()
12173                    }),
12174                    ..Default::default()
12175                },
12176            )]))
12177        })
12178        .next()
12179        .await;
12180    cx.executor().start_waiting();
12181    format.await;
12182    assert_eq!(
12183        editor.update(cx, |editor, cx| editor.text(cx)),
12184        "import { a } from 'module';\n\nconst x = a;\n"
12185    );
12186
12187    editor.update_in(cx, |editor, window, cx| {
12188        editor.set_text(
12189            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12190            window,
12191            cx,
12192        )
12193    });
12194    // Ensure we don't lock if code action hangs.
12195    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12196        move |params, _| async move {
12197            assert_eq!(
12198                params.text_document.uri,
12199                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12200            );
12201            futures::future::pending::<()>().await;
12202            unreachable!()
12203        },
12204    );
12205    let format = editor
12206        .update_in(cx, |editor, window, cx| {
12207            editor.perform_code_action_kind(
12208                project,
12209                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12210                window,
12211                cx,
12212            )
12213        })
12214        .unwrap();
12215    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
12216    cx.executor().start_waiting();
12217    format.await;
12218    assert_eq!(
12219        editor.update(cx, |editor, cx| editor.text(cx)),
12220        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
12221    );
12222}
12223
12224#[gpui::test]
12225async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
12226    init_test(cx, |_| {});
12227
12228    let mut cx = EditorLspTestContext::new_rust(
12229        lsp::ServerCapabilities {
12230            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12231            ..Default::default()
12232        },
12233        cx,
12234    )
12235    .await;
12236
12237    cx.set_state(indoc! {"
12238        one.twoˇ
12239    "});
12240
12241    // The format request takes a long time. When it completes, it inserts
12242    // a newline and an indent before the `.`
12243    cx.lsp
12244        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12245            let executor = cx.background_executor().clone();
12246            async move {
12247                executor.timer(Duration::from_millis(100)).await;
12248                Ok(Some(vec![lsp::TextEdit {
12249                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12250                    new_text: "\n    ".into(),
12251                }]))
12252            }
12253        });
12254
12255    // Submit a format request.
12256    let format_1 = cx
12257        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12258        .unwrap();
12259    cx.executor().run_until_parked();
12260
12261    // Submit a second format request.
12262    let format_2 = cx
12263        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12264        .unwrap();
12265    cx.executor().run_until_parked();
12266
12267    // Wait for both format requests to complete
12268    cx.executor().advance_clock(Duration::from_millis(200));
12269    cx.executor().start_waiting();
12270    format_1.await.unwrap();
12271    cx.executor().start_waiting();
12272    format_2.await.unwrap();
12273
12274    // The formatting edits only happens once.
12275    cx.assert_editor_state(indoc! {"
12276        one
12277            .twoˇ
12278    "});
12279}
12280
12281#[gpui::test]
12282async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12283    init_test(cx, |settings| {
12284        settings.defaults.formatter = Some(SelectedFormatter::Auto)
12285    });
12286
12287    let mut cx = EditorLspTestContext::new_rust(
12288        lsp::ServerCapabilities {
12289            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12290            ..Default::default()
12291        },
12292        cx,
12293    )
12294    .await;
12295
12296    // Set up a buffer white some trailing whitespace and no trailing newline.
12297    cx.set_state(
12298        &[
12299            "one ",   //
12300            "twoˇ",   //
12301            "three ", //
12302            "four",   //
12303        ]
12304        .join("\n"),
12305    );
12306
12307    // Submit a format request.
12308    let format = cx
12309        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12310        .unwrap();
12311
12312    // Record which buffer changes have been sent to the language server
12313    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12314    cx.lsp
12315        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12316            let buffer_changes = buffer_changes.clone();
12317            move |params, _| {
12318                buffer_changes.lock().extend(
12319                    params
12320                        .content_changes
12321                        .into_iter()
12322                        .map(|e| (e.range.unwrap(), e.text)),
12323                );
12324            }
12325        });
12326
12327    // Handle formatting requests to the language server.
12328    cx.lsp
12329        .set_request_handler::<lsp::request::Formatting, _, _>({
12330            let buffer_changes = buffer_changes.clone();
12331            move |_, _| {
12332                // When formatting is requested, trailing whitespace has already been stripped,
12333                // and the trailing newline has already been added.
12334                assert_eq!(
12335                    &buffer_changes.lock()[1..],
12336                    &[
12337                        (
12338                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12339                            "".into()
12340                        ),
12341                        (
12342                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12343                            "".into()
12344                        ),
12345                        (
12346                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12347                            "\n".into()
12348                        ),
12349                    ]
12350                );
12351
12352                // Insert blank lines between each line of the buffer.
12353                async move {
12354                    Ok(Some(vec![
12355                        lsp::TextEdit {
12356                            range: lsp::Range::new(
12357                                lsp::Position::new(1, 0),
12358                                lsp::Position::new(1, 0),
12359                            ),
12360                            new_text: "\n".into(),
12361                        },
12362                        lsp::TextEdit {
12363                            range: lsp::Range::new(
12364                                lsp::Position::new(2, 0),
12365                                lsp::Position::new(2, 0),
12366                            ),
12367                            new_text: "\n".into(),
12368                        },
12369                    ]))
12370                }
12371            }
12372        });
12373
12374    // After formatting the buffer, the trailing whitespace is stripped,
12375    // a newline is appended, and the edits provided by the language server
12376    // have been applied.
12377    format.await.unwrap();
12378    cx.assert_editor_state(
12379        &[
12380            "one",   //
12381            "",      //
12382            "twoˇ",  //
12383            "",      //
12384            "three", //
12385            "four",  //
12386            "",      //
12387        ]
12388        .join("\n"),
12389    );
12390
12391    // Undoing the formatting undoes the trailing whitespace removal, the
12392    // trailing newline, and the LSP edits.
12393    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12394    cx.assert_editor_state(
12395        &[
12396            "one ",   //
12397            "twoˇ",   //
12398            "three ", //
12399            "four",   //
12400        ]
12401        .join("\n"),
12402    );
12403}
12404
12405#[gpui::test]
12406async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12407    cx: &mut TestAppContext,
12408) {
12409    init_test(cx, |_| {});
12410
12411    cx.update(|cx| {
12412        cx.update_global::<SettingsStore, _>(|settings, cx| {
12413            settings.update_user_settings(cx, |settings| {
12414                settings.editor.auto_signature_help = Some(true);
12415            });
12416        });
12417    });
12418
12419    let mut cx = EditorLspTestContext::new_rust(
12420        lsp::ServerCapabilities {
12421            signature_help_provider: Some(lsp::SignatureHelpOptions {
12422                ..Default::default()
12423            }),
12424            ..Default::default()
12425        },
12426        cx,
12427    )
12428    .await;
12429
12430    let language = Language::new(
12431        LanguageConfig {
12432            name: "Rust".into(),
12433            brackets: BracketPairConfig {
12434                pairs: vec![
12435                    BracketPair {
12436                        start: "{".to_string(),
12437                        end: "}".to_string(),
12438                        close: true,
12439                        surround: true,
12440                        newline: true,
12441                    },
12442                    BracketPair {
12443                        start: "(".to_string(),
12444                        end: ")".to_string(),
12445                        close: true,
12446                        surround: true,
12447                        newline: true,
12448                    },
12449                    BracketPair {
12450                        start: "/*".to_string(),
12451                        end: " */".to_string(),
12452                        close: true,
12453                        surround: true,
12454                        newline: true,
12455                    },
12456                    BracketPair {
12457                        start: "[".to_string(),
12458                        end: "]".to_string(),
12459                        close: false,
12460                        surround: false,
12461                        newline: true,
12462                    },
12463                    BracketPair {
12464                        start: "\"".to_string(),
12465                        end: "\"".to_string(),
12466                        close: true,
12467                        surround: true,
12468                        newline: false,
12469                    },
12470                    BracketPair {
12471                        start: "<".to_string(),
12472                        end: ">".to_string(),
12473                        close: false,
12474                        surround: true,
12475                        newline: true,
12476                    },
12477                ],
12478                ..Default::default()
12479            },
12480            autoclose_before: "})]".to_string(),
12481            ..Default::default()
12482        },
12483        Some(tree_sitter_rust::LANGUAGE.into()),
12484    );
12485    let language = Arc::new(language);
12486
12487    cx.language_registry().add(language.clone());
12488    cx.update_buffer(|buffer, cx| {
12489        buffer.set_language(Some(language), cx);
12490    });
12491
12492    cx.set_state(
12493        &r#"
12494            fn main() {
12495                sampleˇ
12496            }
12497        "#
12498        .unindent(),
12499    );
12500
12501    cx.update_editor(|editor, window, cx| {
12502        editor.handle_input("(", window, cx);
12503    });
12504    cx.assert_editor_state(
12505        &"
12506            fn main() {
12507                sample(ˇ)
12508            }
12509        "
12510        .unindent(),
12511    );
12512
12513    let mocked_response = lsp::SignatureHelp {
12514        signatures: vec![lsp::SignatureInformation {
12515            label: "fn sample(param1: u8, param2: u8)".to_string(),
12516            documentation: None,
12517            parameters: Some(vec![
12518                lsp::ParameterInformation {
12519                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12520                    documentation: None,
12521                },
12522                lsp::ParameterInformation {
12523                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12524                    documentation: None,
12525                },
12526            ]),
12527            active_parameter: None,
12528        }],
12529        active_signature: Some(0),
12530        active_parameter: Some(0),
12531    };
12532    handle_signature_help_request(&mut cx, mocked_response).await;
12533
12534    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12535        .await;
12536
12537    cx.editor(|editor, _, _| {
12538        let signature_help_state = editor.signature_help_state.popover().cloned();
12539        let signature = signature_help_state.unwrap();
12540        assert_eq!(
12541            signature.signatures[signature.current_signature].label,
12542            "fn sample(param1: u8, param2: u8)"
12543        );
12544    });
12545}
12546
12547#[gpui::test]
12548async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
12549    init_test(cx, |_| {});
12550
12551    cx.update(|cx| {
12552        cx.update_global::<SettingsStore, _>(|settings, cx| {
12553            settings.update_user_settings(cx, |settings| {
12554                settings.editor.auto_signature_help = Some(false);
12555                settings.editor.show_signature_help_after_edits = Some(false);
12556            });
12557        });
12558    });
12559
12560    let mut cx = EditorLspTestContext::new_rust(
12561        lsp::ServerCapabilities {
12562            signature_help_provider: Some(lsp::SignatureHelpOptions {
12563                ..Default::default()
12564            }),
12565            ..Default::default()
12566        },
12567        cx,
12568    )
12569    .await;
12570
12571    let language = Language::new(
12572        LanguageConfig {
12573            name: "Rust".into(),
12574            brackets: BracketPairConfig {
12575                pairs: vec![
12576                    BracketPair {
12577                        start: "{".to_string(),
12578                        end: "}".to_string(),
12579                        close: true,
12580                        surround: true,
12581                        newline: true,
12582                    },
12583                    BracketPair {
12584                        start: "(".to_string(),
12585                        end: ")".to_string(),
12586                        close: true,
12587                        surround: true,
12588                        newline: true,
12589                    },
12590                    BracketPair {
12591                        start: "/*".to_string(),
12592                        end: " */".to_string(),
12593                        close: true,
12594                        surround: true,
12595                        newline: true,
12596                    },
12597                    BracketPair {
12598                        start: "[".to_string(),
12599                        end: "]".to_string(),
12600                        close: false,
12601                        surround: false,
12602                        newline: true,
12603                    },
12604                    BracketPair {
12605                        start: "\"".to_string(),
12606                        end: "\"".to_string(),
12607                        close: true,
12608                        surround: true,
12609                        newline: false,
12610                    },
12611                    BracketPair {
12612                        start: "<".to_string(),
12613                        end: ">".to_string(),
12614                        close: false,
12615                        surround: true,
12616                        newline: true,
12617                    },
12618                ],
12619                ..Default::default()
12620            },
12621            autoclose_before: "})]".to_string(),
12622            ..Default::default()
12623        },
12624        Some(tree_sitter_rust::LANGUAGE.into()),
12625    );
12626    let language = Arc::new(language);
12627
12628    cx.language_registry().add(language.clone());
12629    cx.update_buffer(|buffer, cx| {
12630        buffer.set_language(Some(language), cx);
12631    });
12632
12633    // Ensure that signature_help is not called when no signature help is enabled.
12634    cx.set_state(
12635        &r#"
12636            fn main() {
12637                sampleˇ
12638            }
12639        "#
12640        .unindent(),
12641    );
12642    cx.update_editor(|editor, window, cx| {
12643        editor.handle_input("(", window, cx);
12644    });
12645    cx.assert_editor_state(
12646        &"
12647            fn main() {
12648                sample(ˇ)
12649            }
12650        "
12651        .unindent(),
12652    );
12653    cx.editor(|editor, _, _| {
12654        assert!(editor.signature_help_state.task().is_none());
12655    });
12656
12657    let mocked_response = lsp::SignatureHelp {
12658        signatures: vec![lsp::SignatureInformation {
12659            label: "fn sample(param1: u8, param2: u8)".to_string(),
12660            documentation: None,
12661            parameters: Some(vec![
12662                lsp::ParameterInformation {
12663                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12664                    documentation: None,
12665                },
12666                lsp::ParameterInformation {
12667                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12668                    documentation: None,
12669                },
12670            ]),
12671            active_parameter: None,
12672        }],
12673        active_signature: Some(0),
12674        active_parameter: Some(0),
12675    };
12676
12677    // Ensure that signature_help is called when enabled afte edits
12678    cx.update(|_, cx| {
12679        cx.update_global::<SettingsStore, _>(|settings, cx| {
12680            settings.update_user_settings(cx, |settings| {
12681                settings.editor.auto_signature_help = Some(false);
12682                settings.editor.show_signature_help_after_edits = Some(true);
12683            });
12684        });
12685    });
12686    cx.set_state(
12687        &r#"
12688            fn main() {
12689                sampleˇ
12690            }
12691        "#
12692        .unindent(),
12693    );
12694    cx.update_editor(|editor, window, cx| {
12695        editor.handle_input("(", window, cx);
12696    });
12697    cx.assert_editor_state(
12698        &"
12699            fn main() {
12700                sample(ˇ)
12701            }
12702        "
12703        .unindent(),
12704    );
12705    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12706    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12707        .await;
12708    cx.update_editor(|editor, _, _| {
12709        let signature_help_state = editor.signature_help_state.popover().cloned();
12710        assert!(signature_help_state.is_some());
12711        let signature = signature_help_state.unwrap();
12712        assert_eq!(
12713            signature.signatures[signature.current_signature].label,
12714            "fn sample(param1: u8, param2: u8)"
12715        );
12716        editor.signature_help_state = SignatureHelpState::default();
12717    });
12718
12719    // Ensure that signature_help is called when auto signature help override is enabled
12720    cx.update(|_, cx| {
12721        cx.update_global::<SettingsStore, _>(|settings, cx| {
12722            settings.update_user_settings(cx, |settings| {
12723                settings.editor.auto_signature_help = Some(true);
12724                settings.editor.show_signature_help_after_edits = Some(false);
12725            });
12726        });
12727    });
12728    cx.set_state(
12729        &r#"
12730            fn main() {
12731                sampleˇ
12732            }
12733        "#
12734        .unindent(),
12735    );
12736    cx.update_editor(|editor, window, cx| {
12737        editor.handle_input("(", window, cx);
12738    });
12739    cx.assert_editor_state(
12740        &"
12741            fn main() {
12742                sample(ˇ)
12743            }
12744        "
12745        .unindent(),
12746    );
12747    handle_signature_help_request(&mut cx, mocked_response).await;
12748    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12749        .await;
12750    cx.editor(|editor, _, _| {
12751        let signature_help_state = editor.signature_help_state.popover().cloned();
12752        assert!(signature_help_state.is_some());
12753        let signature = signature_help_state.unwrap();
12754        assert_eq!(
12755            signature.signatures[signature.current_signature].label,
12756            "fn sample(param1: u8, param2: u8)"
12757        );
12758    });
12759}
12760
12761#[gpui::test]
12762async fn test_signature_help(cx: &mut TestAppContext) {
12763    init_test(cx, |_| {});
12764    cx.update(|cx| {
12765        cx.update_global::<SettingsStore, _>(|settings, cx| {
12766            settings.update_user_settings(cx, |settings| {
12767                settings.editor.auto_signature_help = Some(true);
12768            });
12769        });
12770    });
12771
12772    let mut cx = EditorLspTestContext::new_rust(
12773        lsp::ServerCapabilities {
12774            signature_help_provider: Some(lsp::SignatureHelpOptions {
12775                ..Default::default()
12776            }),
12777            ..Default::default()
12778        },
12779        cx,
12780    )
12781    .await;
12782
12783    // A test that directly calls `show_signature_help`
12784    cx.update_editor(|editor, window, cx| {
12785        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12786    });
12787
12788    let mocked_response = lsp::SignatureHelp {
12789        signatures: vec![lsp::SignatureInformation {
12790            label: "fn sample(param1: u8, param2: u8)".to_string(),
12791            documentation: None,
12792            parameters: Some(vec![
12793                lsp::ParameterInformation {
12794                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12795                    documentation: None,
12796                },
12797                lsp::ParameterInformation {
12798                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12799                    documentation: None,
12800                },
12801            ]),
12802            active_parameter: None,
12803        }],
12804        active_signature: Some(0),
12805        active_parameter: Some(0),
12806    };
12807    handle_signature_help_request(&mut cx, mocked_response).await;
12808
12809    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12810        .await;
12811
12812    cx.editor(|editor, _, _| {
12813        let signature_help_state = editor.signature_help_state.popover().cloned();
12814        assert!(signature_help_state.is_some());
12815        let signature = signature_help_state.unwrap();
12816        assert_eq!(
12817            signature.signatures[signature.current_signature].label,
12818            "fn sample(param1: u8, param2: u8)"
12819        );
12820    });
12821
12822    // When exiting outside from inside the brackets, `signature_help` is closed.
12823    cx.set_state(indoc! {"
12824        fn main() {
12825            sample(ˇ);
12826        }
12827
12828        fn sample(param1: u8, param2: u8) {}
12829    "});
12830
12831    cx.update_editor(|editor, window, cx| {
12832        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12833            s.select_ranges([0..0])
12834        });
12835    });
12836
12837    let mocked_response = lsp::SignatureHelp {
12838        signatures: Vec::new(),
12839        active_signature: None,
12840        active_parameter: None,
12841    };
12842    handle_signature_help_request(&mut cx, mocked_response).await;
12843
12844    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
12845        .await;
12846
12847    cx.editor(|editor, _, _| {
12848        assert!(!editor.signature_help_state.is_shown());
12849    });
12850
12851    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
12852    cx.set_state(indoc! {"
12853        fn main() {
12854            sample(ˇ);
12855        }
12856
12857        fn sample(param1: u8, param2: u8) {}
12858    "});
12859
12860    let mocked_response = lsp::SignatureHelp {
12861        signatures: vec![lsp::SignatureInformation {
12862            label: "fn sample(param1: u8, param2: u8)".to_string(),
12863            documentation: None,
12864            parameters: Some(vec![
12865                lsp::ParameterInformation {
12866                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12867                    documentation: None,
12868                },
12869                lsp::ParameterInformation {
12870                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12871                    documentation: None,
12872                },
12873            ]),
12874            active_parameter: None,
12875        }],
12876        active_signature: Some(0),
12877        active_parameter: Some(0),
12878    };
12879    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12880    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12881        .await;
12882    cx.editor(|editor, _, _| {
12883        assert!(editor.signature_help_state.is_shown());
12884    });
12885
12886    // Restore the popover with more parameter input
12887    cx.set_state(indoc! {"
12888        fn main() {
12889            sample(param1, param2ˇ);
12890        }
12891
12892        fn sample(param1: u8, param2: u8) {}
12893    "});
12894
12895    let mocked_response = lsp::SignatureHelp {
12896        signatures: vec![lsp::SignatureInformation {
12897            label: "fn sample(param1: u8, param2: u8)".to_string(),
12898            documentation: None,
12899            parameters: Some(vec![
12900                lsp::ParameterInformation {
12901                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12902                    documentation: None,
12903                },
12904                lsp::ParameterInformation {
12905                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12906                    documentation: None,
12907                },
12908            ]),
12909            active_parameter: None,
12910        }],
12911        active_signature: Some(0),
12912        active_parameter: Some(1),
12913    };
12914    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12915    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12916        .await;
12917
12918    // When selecting a range, the popover is gone.
12919    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
12920    cx.update_editor(|editor, window, cx| {
12921        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12922            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
12923        })
12924    });
12925    cx.assert_editor_state(indoc! {"
12926        fn main() {
12927            sample(param1, «ˇparam2»);
12928        }
12929
12930        fn sample(param1: u8, param2: u8) {}
12931    "});
12932    cx.editor(|editor, _, _| {
12933        assert!(!editor.signature_help_state.is_shown());
12934    });
12935
12936    // When unselecting again, the popover is back if within the brackets.
12937    cx.update_editor(|editor, window, cx| {
12938        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12939            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
12940        })
12941    });
12942    cx.assert_editor_state(indoc! {"
12943        fn main() {
12944            sample(param1, ˇparam2);
12945        }
12946
12947        fn sample(param1: u8, param2: u8) {}
12948    "});
12949    handle_signature_help_request(&mut cx, mocked_response).await;
12950    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12951        .await;
12952    cx.editor(|editor, _, _| {
12953        assert!(editor.signature_help_state.is_shown());
12954    });
12955
12956    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
12957    cx.update_editor(|editor, window, cx| {
12958        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12959            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
12960            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
12961        })
12962    });
12963    cx.assert_editor_state(indoc! {"
12964        fn main() {
12965            sample(param1, ˇparam2);
12966        }
12967
12968        fn sample(param1: u8, param2: u8) {}
12969    "});
12970
12971    let mocked_response = lsp::SignatureHelp {
12972        signatures: vec![lsp::SignatureInformation {
12973            label: "fn sample(param1: u8, param2: u8)".to_string(),
12974            documentation: None,
12975            parameters: Some(vec![
12976                lsp::ParameterInformation {
12977                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12978                    documentation: None,
12979                },
12980                lsp::ParameterInformation {
12981                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12982                    documentation: None,
12983                },
12984            ]),
12985            active_parameter: None,
12986        }],
12987        active_signature: Some(0),
12988        active_parameter: Some(1),
12989    };
12990    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12991    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12992        .await;
12993    cx.update_editor(|editor, _, cx| {
12994        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
12995    });
12996    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
12997        .await;
12998    cx.update_editor(|editor, window, cx| {
12999        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13000            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13001        })
13002    });
13003    cx.assert_editor_state(indoc! {"
13004        fn main() {
13005            sample(param1, «ˇparam2»);
13006        }
13007
13008        fn sample(param1: u8, param2: u8) {}
13009    "});
13010    cx.update_editor(|editor, window, cx| {
13011        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13012            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13013        })
13014    });
13015    cx.assert_editor_state(indoc! {"
13016        fn main() {
13017            sample(param1, ˇparam2);
13018        }
13019
13020        fn sample(param1: u8, param2: u8) {}
13021    "});
13022    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13023        .await;
13024}
13025
13026#[gpui::test]
13027async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13028    init_test(cx, |_| {});
13029
13030    let mut cx = EditorLspTestContext::new_rust(
13031        lsp::ServerCapabilities {
13032            signature_help_provider: Some(lsp::SignatureHelpOptions {
13033                ..Default::default()
13034            }),
13035            ..Default::default()
13036        },
13037        cx,
13038    )
13039    .await;
13040
13041    cx.set_state(indoc! {"
13042        fn main() {
13043            overloadedˇ
13044        }
13045    "});
13046
13047    cx.update_editor(|editor, window, cx| {
13048        editor.handle_input("(", window, cx);
13049        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13050    });
13051
13052    // Mock response with 3 signatures
13053    let mocked_response = lsp::SignatureHelp {
13054        signatures: vec![
13055            lsp::SignatureInformation {
13056                label: "fn overloaded(x: i32)".to_string(),
13057                documentation: None,
13058                parameters: Some(vec![lsp::ParameterInformation {
13059                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13060                    documentation: None,
13061                }]),
13062                active_parameter: None,
13063            },
13064            lsp::SignatureInformation {
13065                label: "fn overloaded(x: i32, y: i32)".to_string(),
13066                documentation: None,
13067                parameters: Some(vec![
13068                    lsp::ParameterInformation {
13069                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13070                        documentation: None,
13071                    },
13072                    lsp::ParameterInformation {
13073                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13074                        documentation: None,
13075                    },
13076                ]),
13077                active_parameter: None,
13078            },
13079            lsp::SignatureInformation {
13080                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13081                documentation: None,
13082                parameters: Some(vec![
13083                    lsp::ParameterInformation {
13084                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13085                        documentation: None,
13086                    },
13087                    lsp::ParameterInformation {
13088                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13089                        documentation: None,
13090                    },
13091                    lsp::ParameterInformation {
13092                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13093                        documentation: None,
13094                    },
13095                ]),
13096                active_parameter: None,
13097            },
13098        ],
13099        active_signature: Some(1),
13100        active_parameter: Some(0),
13101    };
13102    handle_signature_help_request(&mut cx, mocked_response).await;
13103
13104    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13105        .await;
13106
13107    // Verify we have multiple signatures and the right one is selected
13108    cx.editor(|editor, _, _| {
13109        let popover = editor.signature_help_state.popover().cloned().unwrap();
13110        assert_eq!(popover.signatures.len(), 3);
13111        // active_signature was 1, so that should be the current
13112        assert_eq!(popover.current_signature, 1);
13113        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13114        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13115        assert_eq!(
13116            popover.signatures[2].label,
13117            "fn overloaded(x: i32, y: i32, z: i32)"
13118        );
13119    });
13120
13121    // Test navigation functionality
13122    cx.update_editor(|editor, window, cx| {
13123        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13124    });
13125
13126    cx.editor(|editor, _, _| {
13127        let popover = editor.signature_help_state.popover().cloned().unwrap();
13128        assert_eq!(popover.current_signature, 2);
13129    });
13130
13131    // Test wrap around
13132    cx.update_editor(|editor, window, cx| {
13133        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13134    });
13135
13136    cx.editor(|editor, _, _| {
13137        let popover = editor.signature_help_state.popover().cloned().unwrap();
13138        assert_eq!(popover.current_signature, 0);
13139    });
13140
13141    // Test previous navigation
13142    cx.update_editor(|editor, window, cx| {
13143        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13144    });
13145
13146    cx.editor(|editor, _, _| {
13147        let popover = editor.signature_help_state.popover().cloned().unwrap();
13148        assert_eq!(popover.current_signature, 2);
13149    });
13150}
13151
13152#[gpui::test]
13153async fn test_completion_mode(cx: &mut TestAppContext) {
13154    init_test(cx, |_| {});
13155    let mut cx = EditorLspTestContext::new_rust(
13156        lsp::ServerCapabilities {
13157            completion_provider: Some(lsp::CompletionOptions {
13158                resolve_provider: Some(true),
13159                ..Default::default()
13160            }),
13161            ..Default::default()
13162        },
13163        cx,
13164    )
13165    .await;
13166
13167    struct Run {
13168        run_description: &'static str,
13169        initial_state: String,
13170        buffer_marked_text: String,
13171        completion_label: &'static str,
13172        completion_text: &'static str,
13173        expected_with_insert_mode: String,
13174        expected_with_replace_mode: String,
13175        expected_with_replace_subsequence_mode: String,
13176        expected_with_replace_suffix_mode: String,
13177    }
13178
13179    let runs = [
13180        Run {
13181            run_description: "Start of word matches completion text",
13182            initial_state: "before ediˇ after".into(),
13183            buffer_marked_text: "before <edi|> after".into(),
13184            completion_label: "editor",
13185            completion_text: "editor",
13186            expected_with_insert_mode: "before editorˇ after".into(),
13187            expected_with_replace_mode: "before editorˇ after".into(),
13188            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13189            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13190        },
13191        Run {
13192            run_description: "Accept same text at the middle of the word",
13193            initial_state: "before ediˇtor after".into(),
13194            buffer_marked_text: "before <edi|tor> after".into(),
13195            completion_label: "editor",
13196            completion_text: "editor",
13197            expected_with_insert_mode: "before editorˇtor after".into(),
13198            expected_with_replace_mode: "before editorˇ after".into(),
13199            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13200            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13201        },
13202        Run {
13203            run_description: "End of word matches completion text -- cursor at end",
13204            initial_state: "before torˇ after".into(),
13205            buffer_marked_text: "before <tor|> after".into(),
13206            completion_label: "editor",
13207            completion_text: "editor",
13208            expected_with_insert_mode: "before editorˇ after".into(),
13209            expected_with_replace_mode: "before editorˇ after".into(),
13210            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13211            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13212        },
13213        Run {
13214            run_description: "End of word matches completion text -- cursor at start",
13215            initial_state: "before ˇtor after".into(),
13216            buffer_marked_text: "before <|tor> after".into(),
13217            completion_label: "editor",
13218            completion_text: "editor",
13219            expected_with_insert_mode: "before editorˇtor after".into(),
13220            expected_with_replace_mode: "before editorˇ after".into(),
13221            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13222            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13223        },
13224        Run {
13225            run_description: "Prepend text containing whitespace",
13226            initial_state: "pˇfield: bool".into(),
13227            buffer_marked_text: "<p|field>: bool".into(),
13228            completion_label: "pub ",
13229            completion_text: "pub ",
13230            expected_with_insert_mode: "pub ˇfield: bool".into(),
13231            expected_with_replace_mode: "pub ˇ: bool".into(),
13232            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13233            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13234        },
13235        Run {
13236            run_description: "Add element to start of list",
13237            initial_state: "[element_ˇelement_2]".into(),
13238            buffer_marked_text: "[<element_|element_2>]".into(),
13239            completion_label: "element_1",
13240            completion_text: "element_1",
13241            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13242            expected_with_replace_mode: "[element_1ˇ]".into(),
13243            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13244            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13245        },
13246        Run {
13247            run_description: "Add element to start of list -- first and second elements are equal",
13248            initial_state: "[elˇelement]".into(),
13249            buffer_marked_text: "[<el|element>]".into(),
13250            completion_label: "element",
13251            completion_text: "element",
13252            expected_with_insert_mode: "[elementˇelement]".into(),
13253            expected_with_replace_mode: "[elementˇ]".into(),
13254            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13255            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13256        },
13257        Run {
13258            run_description: "Ends with matching suffix",
13259            initial_state: "SubˇError".into(),
13260            buffer_marked_text: "<Sub|Error>".into(),
13261            completion_label: "SubscriptionError",
13262            completion_text: "SubscriptionError",
13263            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13264            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13265            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13266            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13267        },
13268        Run {
13269            run_description: "Suffix is a subsequence -- contiguous",
13270            initial_state: "SubˇErr".into(),
13271            buffer_marked_text: "<Sub|Err>".into(),
13272            completion_label: "SubscriptionError",
13273            completion_text: "SubscriptionError",
13274            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13275            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13276            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13277            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13278        },
13279        Run {
13280            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13281            initial_state: "Suˇscrirr".into(),
13282            buffer_marked_text: "<Su|scrirr>".into(),
13283            completion_label: "SubscriptionError",
13284            completion_text: "SubscriptionError",
13285            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13286            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13287            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13288            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13289        },
13290        Run {
13291            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13292            initial_state: "foo(indˇix)".into(),
13293            buffer_marked_text: "foo(<ind|ix>)".into(),
13294            completion_label: "node_index",
13295            completion_text: "node_index",
13296            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13297            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13298            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13299            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13300        },
13301        Run {
13302            run_description: "Replace range ends before cursor - should extend to cursor",
13303            initial_state: "before editˇo after".into(),
13304            buffer_marked_text: "before <{ed}>it|o after".into(),
13305            completion_label: "editor",
13306            completion_text: "editor",
13307            expected_with_insert_mode: "before editorˇo after".into(),
13308            expected_with_replace_mode: "before editorˇo after".into(),
13309            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13310            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13311        },
13312        Run {
13313            run_description: "Uses label for suffix matching",
13314            initial_state: "before ediˇtor after".into(),
13315            buffer_marked_text: "before <edi|tor> after".into(),
13316            completion_label: "editor",
13317            completion_text: "editor()",
13318            expected_with_insert_mode: "before editor()ˇtor after".into(),
13319            expected_with_replace_mode: "before editor()ˇ after".into(),
13320            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13321            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13322        },
13323        Run {
13324            run_description: "Case insensitive subsequence and suffix matching",
13325            initial_state: "before EDiˇtoR after".into(),
13326            buffer_marked_text: "before <EDi|toR> after".into(),
13327            completion_label: "editor",
13328            completion_text: "editor",
13329            expected_with_insert_mode: "before editorˇtoR after".into(),
13330            expected_with_replace_mode: "before editorˇ after".into(),
13331            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13332            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13333        },
13334    ];
13335
13336    for run in runs {
13337        let run_variations = [
13338            (LspInsertMode::Insert, run.expected_with_insert_mode),
13339            (LspInsertMode::Replace, run.expected_with_replace_mode),
13340            (
13341                LspInsertMode::ReplaceSubsequence,
13342                run.expected_with_replace_subsequence_mode,
13343            ),
13344            (
13345                LspInsertMode::ReplaceSuffix,
13346                run.expected_with_replace_suffix_mode,
13347            ),
13348        ];
13349
13350        for (lsp_insert_mode, expected_text) in run_variations {
13351            eprintln!(
13352                "run = {:?}, mode = {lsp_insert_mode:.?}",
13353                run.run_description,
13354            );
13355
13356            update_test_language_settings(&mut cx, |settings| {
13357                settings.defaults.completions = Some(CompletionSettingsContent {
13358                    lsp_insert_mode: Some(lsp_insert_mode),
13359                    words: Some(WordsCompletionMode::Disabled),
13360                    words_min_length: Some(0),
13361                    ..Default::default()
13362                });
13363            });
13364
13365            cx.set_state(&run.initial_state);
13366            cx.update_editor(|editor, window, cx| {
13367                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13368            });
13369
13370            let counter = Arc::new(AtomicUsize::new(0));
13371            handle_completion_request_with_insert_and_replace(
13372                &mut cx,
13373                &run.buffer_marked_text,
13374                vec![(run.completion_label, run.completion_text)],
13375                counter.clone(),
13376            )
13377            .await;
13378            cx.condition(|editor, _| editor.context_menu_visible())
13379                .await;
13380            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13381
13382            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13383                editor
13384                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13385                    .unwrap()
13386            });
13387            cx.assert_editor_state(&expected_text);
13388            handle_resolve_completion_request(&mut cx, None).await;
13389            apply_additional_edits.await.unwrap();
13390        }
13391    }
13392}
13393
13394#[gpui::test]
13395async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13396    init_test(cx, |_| {});
13397    let mut cx = EditorLspTestContext::new_rust(
13398        lsp::ServerCapabilities {
13399            completion_provider: Some(lsp::CompletionOptions {
13400                resolve_provider: Some(true),
13401                ..Default::default()
13402            }),
13403            ..Default::default()
13404        },
13405        cx,
13406    )
13407    .await;
13408
13409    let initial_state = "SubˇError";
13410    let buffer_marked_text = "<Sub|Error>";
13411    let completion_text = "SubscriptionError";
13412    let expected_with_insert_mode = "SubscriptionErrorˇError";
13413    let expected_with_replace_mode = "SubscriptionErrorˇ";
13414
13415    update_test_language_settings(&mut cx, |settings| {
13416        settings.defaults.completions = Some(CompletionSettingsContent {
13417            words: Some(WordsCompletionMode::Disabled),
13418            words_min_length: Some(0),
13419            // set the opposite here to ensure that the action is overriding the default behavior
13420            lsp_insert_mode: Some(LspInsertMode::Insert),
13421            ..Default::default()
13422        });
13423    });
13424
13425    cx.set_state(initial_state);
13426    cx.update_editor(|editor, window, cx| {
13427        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13428    });
13429
13430    let counter = Arc::new(AtomicUsize::new(0));
13431    handle_completion_request_with_insert_and_replace(
13432        &mut cx,
13433        buffer_marked_text,
13434        vec![(completion_text, completion_text)],
13435        counter.clone(),
13436    )
13437    .await;
13438    cx.condition(|editor, _| editor.context_menu_visible())
13439        .await;
13440    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13441
13442    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13443        editor
13444            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13445            .unwrap()
13446    });
13447    cx.assert_editor_state(expected_with_replace_mode);
13448    handle_resolve_completion_request(&mut cx, None).await;
13449    apply_additional_edits.await.unwrap();
13450
13451    update_test_language_settings(&mut cx, |settings| {
13452        settings.defaults.completions = Some(CompletionSettingsContent {
13453            words: Some(WordsCompletionMode::Disabled),
13454            words_min_length: Some(0),
13455            // set the opposite here to ensure that the action is overriding the default behavior
13456            lsp_insert_mode: Some(LspInsertMode::Replace),
13457            ..Default::default()
13458        });
13459    });
13460
13461    cx.set_state(initial_state);
13462    cx.update_editor(|editor, window, cx| {
13463        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13464    });
13465    handle_completion_request_with_insert_and_replace(
13466        &mut cx,
13467        buffer_marked_text,
13468        vec![(completion_text, completion_text)],
13469        counter.clone(),
13470    )
13471    .await;
13472    cx.condition(|editor, _| editor.context_menu_visible())
13473        .await;
13474    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13475
13476    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13477        editor
13478            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13479            .unwrap()
13480    });
13481    cx.assert_editor_state(expected_with_insert_mode);
13482    handle_resolve_completion_request(&mut cx, None).await;
13483    apply_additional_edits.await.unwrap();
13484}
13485
13486#[gpui::test]
13487async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
13488    init_test(cx, |_| {});
13489    let mut cx = EditorLspTestContext::new_rust(
13490        lsp::ServerCapabilities {
13491            completion_provider: Some(lsp::CompletionOptions {
13492                resolve_provider: Some(true),
13493                ..Default::default()
13494            }),
13495            ..Default::default()
13496        },
13497        cx,
13498    )
13499    .await;
13500
13501    // scenario: surrounding text matches completion text
13502    let completion_text = "to_offset";
13503    let initial_state = indoc! {"
13504        1. buf.to_offˇsuffix
13505        2. buf.to_offˇsuf
13506        3. buf.to_offˇfix
13507        4. buf.to_offˇ
13508        5. into_offˇensive
13509        6. ˇsuffix
13510        7. let ˇ //
13511        8. aaˇzz
13512        9. buf.to_off«zzzzzˇ»suffix
13513        10. buf.«ˇzzzzz»suffix
13514        11. to_off«ˇzzzzz»
13515
13516        buf.to_offˇsuffix  // newest cursor
13517    "};
13518    let completion_marked_buffer = indoc! {"
13519        1. buf.to_offsuffix
13520        2. buf.to_offsuf
13521        3. buf.to_offfix
13522        4. buf.to_off
13523        5. into_offensive
13524        6. suffix
13525        7. let  //
13526        8. aazz
13527        9. buf.to_offzzzzzsuffix
13528        10. buf.zzzzzsuffix
13529        11. to_offzzzzz
13530
13531        buf.<to_off|suffix>  // newest cursor
13532    "};
13533    let expected = indoc! {"
13534        1. buf.to_offsetˇ
13535        2. buf.to_offsetˇsuf
13536        3. buf.to_offsetˇfix
13537        4. buf.to_offsetˇ
13538        5. into_offsetˇensive
13539        6. to_offsetˇsuffix
13540        7. let to_offsetˇ //
13541        8. aato_offsetˇzz
13542        9. buf.to_offsetˇ
13543        10. buf.to_offsetˇsuffix
13544        11. to_offsetˇ
13545
13546        buf.to_offsetˇ  // newest cursor
13547    "};
13548    cx.set_state(initial_state);
13549    cx.update_editor(|editor, window, cx| {
13550        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13551    });
13552    handle_completion_request_with_insert_and_replace(
13553        &mut cx,
13554        completion_marked_buffer,
13555        vec![(completion_text, completion_text)],
13556        Arc::new(AtomicUsize::new(0)),
13557    )
13558    .await;
13559    cx.condition(|editor, _| editor.context_menu_visible())
13560        .await;
13561    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13562        editor
13563            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13564            .unwrap()
13565    });
13566    cx.assert_editor_state(expected);
13567    handle_resolve_completion_request(&mut cx, None).await;
13568    apply_additional_edits.await.unwrap();
13569
13570    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
13571    let completion_text = "foo_and_bar";
13572    let initial_state = indoc! {"
13573        1. ooanbˇ
13574        2. zooanbˇ
13575        3. ooanbˇz
13576        4. zooanbˇz
13577        5. ooanˇ
13578        6. oanbˇ
13579
13580        ooanbˇ
13581    "};
13582    let completion_marked_buffer = indoc! {"
13583        1. ooanb
13584        2. zooanb
13585        3. ooanbz
13586        4. zooanbz
13587        5. ooan
13588        6. oanb
13589
13590        <ooanb|>
13591    "};
13592    let expected = indoc! {"
13593        1. foo_and_barˇ
13594        2. zfoo_and_barˇ
13595        3. foo_and_barˇz
13596        4. zfoo_and_barˇz
13597        5. ooanfoo_and_barˇ
13598        6. oanbfoo_and_barˇ
13599
13600        foo_and_barˇ
13601    "};
13602    cx.set_state(initial_state);
13603    cx.update_editor(|editor, window, cx| {
13604        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13605    });
13606    handle_completion_request_with_insert_and_replace(
13607        &mut cx,
13608        completion_marked_buffer,
13609        vec![(completion_text, completion_text)],
13610        Arc::new(AtomicUsize::new(0)),
13611    )
13612    .await;
13613    cx.condition(|editor, _| editor.context_menu_visible())
13614        .await;
13615    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13616        editor
13617            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13618            .unwrap()
13619    });
13620    cx.assert_editor_state(expected);
13621    handle_resolve_completion_request(&mut cx, None).await;
13622    apply_additional_edits.await.unwrap();
13623
13624    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
13625    // (expects the same as if it was inserted at the end)
13626    let completion_text = "foo_and_bar";
13627    let initial_state = indoc! {"
13628        1. ooˇanb
13629        2. zooˇanb
13630        3. ooˇanbz
13631        4. zooˇanbz
13632
13633        ooˇanb
13634    "};
13635    let completion_marked_buffer = indoc! {"
13636        1. ooanb
13637        2. zooanb
13638        3. ooanbz
13639        4. zooanbz
13640
13641        <oo|anb>
13642    "};
13643    let expected = indoc! {"
13644        1. foo_and_barˇ
13645        2. zfoo_and_barˇ
13646        3. foo_and_barˇz
13647        4. zfoo_and_barˇz
13648
13649        foo_and_barˇ
13650    "};
13651    cx.set_state(initial_state);
13652    cx.update_editor(|editor, window, cx| {
13653        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13654    });
13655    handle_completion_request_with_insert_and_replace(
13656        &mut cx,
13657        completion_marked_buffer,
13658        vec![(completion_text, completion_text)],
13659        Arc::new(AtomicUsize::new(0)),
13660    )
13661    .await;
13662    cx.condition(|editor, _| editor.context_menu_visible())
13663        .await;
13664    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13665        editor
13666            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13667            .unwrap()
13668    });
13669    cx.assert_editor_state(expected);
13670    handle_resolve_completion_request(&mut cx, None).await;
13671    apply_additional_edits.await.unwrap();
13672}
13673
13674// This used to crash
13675#[gpui::test]
13676async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
13677    init_test(cx, |_| {});
13678
13679    let buffer_text = indoc! {"
13680        fn main() {
13681            10.satu;
13682
13683            //
13684            // separate cursors so they open in different excerpts (manually reproducible)
13685            //
13686
13687            10.satu20;
13688        }
13689    "};
13690    let multibuffer_text_with_selections = indoc! {"
13691        fn main() {
13692            10.satuˇ;
13693
13694            //
13695
13696            //
13697
13698            10.satuˇ20;
13699        }
13700    "};
13701    let expected_multibuffer = indoc! {"
13702        fn main() {
13703            10.saturating_sub()ˇ;
13704
13705            //
13706
13707            //
13708
13709            10.saturating_sub()ˇ;
13710        }
13711    "};
13712
13713    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
13714    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
13715
13716    let fs = FakeFs::new(cx.executor());
13717    fs.insert_tree(
13718        path!("/a"),
13719        json!({
13720            "main.rs": buffer_text,
13721        }),
13722    )
13723    .await;
13724
13725    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13726    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13727    language_registry.add(rust_lang());
13728    let mut fake_servers = language_registry.register_fake_lsp(
13729        "Rust",
13730        FakeLspAdapter {
13731            capabilities: lsp::ServerCapabilities {
13732                completion_provider: Some(lsp::CompletionOptions {
13733                    resolve_provider: None,
13734                    ..lsp::CompletionOptions::default()
13735                }),
13736                ..lsp::ServerCapabilities::default()
13737            },
13738            ..FakeLspAdapter::default()
13739        },
13740    );
13741    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13742    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13743    let buffer = project
13744        .update(cx, |project, cx| {
13745            project.open_local_buffer(path!("/a/main.rs"), cx)
13746        })
13747        .await
13748        .unwrap();
13749
13750    let multi_buffer = cx.new(|cx| {
13751        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
13752        multi_buffer.push_excerpts(
13753            buffer.clone(),
13754            [ExcerptRange::new(0..first_excerpt_end)],
13755            cx,
13756        );
13757        multi_buffer.push_excerpts(
13758            buffer.clone(),
13759            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
13760            cx,
13761        );
13762        multi_buffer
13763    });
13764
13765    let editor = workspace
13766        .update(cx, |_, window, cx| {
13767            cx.new(|cx| {
13768                Editor::new(
13769                    EditorMode::Full {
13770                        scale_ui_elements_with_buffer_font_size: false,
13771                        show_active_line_background: false,
13772                        sized_by_content: false,
13773                    },
13774                    multi_buffer.clone(),
13775                    Some(project.clone()),
13776                    window,
13777                    cx,
13778                )
13779            })
13780        })
13781        .unwrap();
13782
13783    let pane = workspace
13784        .update(cx, |workspace, _, _| workspace.active_pane().clone())
13785        .unwrap();
13786    pane.update_in(cx, |pane, window, cx| {
13787        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
13788    });
13789
13790    let fake_server = fake_servers.next().await.unwrap();
13791
13792    editor.update_in(cx, |editor, window, cx| {
13793        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13794            s.select_ranges([
13795                Point::new(1, 11)..Point::new(1, 11),
13796                Point::new(7, 11)..Point::new(7, 11),
13797            ])
13798        });
13799
13800        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
13801    });
13802
13803    editor.update_in(cx, |editor, window, cx| {
13804        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13805    });
13806
13807    fake_server
13808        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13809            let completion_item = lsp::CompletionItem {
13810                label: "saturating_sub()".into(),
13811                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
13812                    lsp::InsertReplaceEdit {
13813                        new_text: "saturating_sub()".to_owned(),
13814                        insert: lsp::Range::new(
13815                            lsp::Position::new(7, 7),
13816                            lsp::Position::new(7, 11),
13817                        ),
13818                        replace: lsp::Range::new(
13819                            lsp::Position::new(7, 7),
13820                            lsp::Position::new(7, 13),
13821                        ),
13822                    },
13823                )),
13824                ..lsp::CompletionItem::default()
13825            };
13826
13827            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
13828        })
13829        .next()
13830        .await
13831        .unwrap();
13832
13833    cx.condition(&editor, |editor, _| editor.context_menu_visible())
13834        .await;
13835
13836    editor
13837        .update_in(cx, |editor, window, cx| {
13838            editor
13839                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13840                .unwrap()
13841        })
13842        .await
13843        .unwrap();
13844
13845    editor.update(cx, |editor, cx| {
13846        assert_text_with_selections(editor, expected_multibuffer, cx);
13847    })
13848}
13849
13850#[gpui::test]
13851async fn test_completion(cx: &mut TestAppContext) {
13852    init_test(cx, |_| {});
13853
13854    let mut cx = EditorLspTestContext::new_rust(
13855        lsp::ServerCapabilities {
13856            completion_provider: Some(lsp::CompletionOptions {
13857                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13858                resolve_provider: Some(true),
13859                ..Default::default()
13860            }),
13861            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13862            ..Default::default()
13863        },
13864        cx,
13865    )
13866    .await;
13867    let counter = Arc::new(AtomicUsize::new(0));
13868
13869    cx.set_state(indoc! {"
13870        oneˇ
13871        two
13872        three
13873    "});
13874    cx.simulate_keystroke(".");
13875    handle_completion_request(
13876        indoc! {"
13877            one.|<>
13878            two
13879            three
13880        "},
13881        vec!["first_completion", "second_completion"],
13882        true,
13883        counter.clone(),
13884        &mut cx,
13885    )
13886    .await;
13887    cx.condition(|editor, _| editor.context_menu_visible())
13888        .await;
13889    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13890
13891    let _handler = handle_signature_help_request(
13892        &mut cx,
13893        lsp::SignatureHelp {
13894            signatures: vec![lsp::SignatureInformation {
13895                label: "test signature".to_string(),
13896                documentation: None,
13897                parameters: Some(vec![lsp::ParameterInformation {
13898                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
13899                    documentation: None,
13900                }]),
13901                active_parameter: None,
13902            }],
13903            active_signature: None,
13904            active_parameter: None,
13905        },
13906    );
13907    cx.update_editor(|editor, window, cx| {
13908        assert!(
13909            !editor.signature_help_state.is_shown(),
13910            "No signature help was called for"
13911        );
13912        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13913    });
13914    cx.run_until_parked();
13915    cx.update_editor(|editor, _, _| {
13916        assert!(
13917            !editor.signature_help_state.is_shown(),
13918            "No signature help should be shown when completions menu is open"
13919        );
13920    });
13921
13922    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13923        editor.context_menu_next(&Default::default(), window, cx);
13924        editor
13925            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13926            .unwrap()
13927    });
13928    cx.assert_editor_state(indoc! {"
13929        one.second_completionˇ
13930        two
13931        three
13932    "});
13933
13934    handle_resolve_completion_request(
13935        &mut cx,
13936        Some(vec![
13937            (
13938                //This overlaps with the primary completion edit which is
13939                //misbehavior from the LSP spec, test that we filter it out
13940                indoc! {"
13941                    one.second_ˇcompletion
13942                    two
13943                    threeˇ
13944                "},
13945                "overlapping additional edit",
13946            ),
13947            (
13948                indoc! {"
13949                    one.second_completion
13950                    two
13951                    threeˇ
13952                "},
13953                "\nadditional edit",
13954            ),
13955        ]),
13956    )
13957    .await;
13958    apply_additional_edits.await.unwrap();
13959    cx.assert_editor_state(indoc! {"
13960        one.second_completionˇ
13961        two
13962        three
13963        additional edit
13964    "});
13965
13966    cx.set_state(indoc! {"
13967        one.second_completion
13968        twoˇ
13969        threeˇ
13970        additional edit
13971    "});
13972    cx.simulate_keystroke(" ");
13973    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13974    cx.simulate_keystroke("s");
13975    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13976
13977    cx.assert_editor_state(indoc! {"
13978        one.second_completion
13979        two sˇ
13980        three sˇ
13981        additional edit
13982    "});
13983    handle_completion_request(
13984        indoc! {"
13985            one.second_completion
13986            two s
13987            three <s|>
13988            additional edit
13989        "},
13990        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
13991        true,
13992        counter.clone(),
13993        &mut cx,
13994    )
13995    .await;
13996    cx.condition(|editor, _| editor.context_menu_visible())
13997        .await;
13998    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13999
14000    cx.simulate_keystroke("i");
14001
14002    handle_completion_request(
14003        indoc! {"
14004            one.second_completion
14005            two si
14006            three <si|>
14007            additional edit
14008        "},
14009        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14010        true,
14011        counter.clone(),
14012        &mut cx,
14013    )
14014    .await;
14015    cx.condition(|editor, _| editor.context_menu_visible())
14016        .await;
14017    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14018
14019    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14020        editor
14021            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14022            .unwrap()
14023    });
14024    cx.assert_editor_state(indoc! {"
14025        one.second_completion
14026        two sixth_completionˇ
14027        three sixth_completionˇ
14028        additional edit
14029    "});
14030
14031    apply_additional_edits.await.unwrap();
14032
14033    update_test_language_settings(&mut cx, |settings| {
14034        settings.defaults.show_completions_on_input = Some(false);
14035    });
14036    cx.set_state("editorˇ");
14037    cx.simulate_keystroke(".");
14038    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14039    cx.simulate_keystrokes("c l o");
14040    cx.assert_editor_state("editor.cloˇ");
14041    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14042    cx.update_editor(|editor, window, cx| {
14043        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14044    });
14045    handle_completion_request(
14046        "editor.<clo|>",
14047        vec!["close", "clobber"],
14048        true,
14049        counter.clone(),
14050        &mut cx,
14051    )
14052    .await;
14053    cx.condition(|editor, _| editor.context_menu_visible())
14054        .await;
14055    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14056
14057    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14058        editor
14059            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14060            .unwrap()
14061    });
14062    cx.assert_editor_state("editor.clobberˇ");
14063    handle_resolve_completion_request(&mut cx, None).await;
14064    apply_additional_edits.await.unwrap();
14065}
14066
14067#[gpui::test]
14068async fn test_completion_reuse(cx: &mut TestAppContext) {
14069    init_test(cx, |_| {});
14070
14071    let mut cx = EditorLspTestContext::new_rust(
14072        lsp::ServerCapabilities {
14073            completion_provider: Some(lsp::CompletionOptions {
14074                trigger_characters: Some(vec![".".to_string()]),
14075                ..Default::default()
14076            }),
14077            ..Default::default()
14078        },
14079        cx,
14080    )
14081    .await;
14082
14083    let counter = Arc::new(AtomicUsize::new(0));
14084    cx.set_state("objˇ");
14085    cx.simulate_keystroke(".");
14086
14087    // Initial completion request returns complete results
14088    let is_incomplete = false;
14089    handle_completion_request(
14090        "obj.|<>",
14091        vec!["a", "ab", "abc"],
14092        is_incomplete,
14093        counter.clone(),
14094        &mut cx,
14095    )
14096    .await;
14097    cx.run_until_parked();
14098    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14099    cx.assert_editor_state("obj.ˇ");
14100    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14101
14102    // Type "a" - filters existing completions
14103    cx.simulate_keystroke("a");
14104    cx.run_until_parked();
14105    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14106    cx.assert_editor_state("obj.aˇ");
14107    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14108
14109    // Type "b" - filters existing completions
14110    cx.simulate_keystroke("b");
14111    cx.run_until_parked();
14112    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14113    cx.assert_editor_state("obj.abˇ");
14114    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14115
14116    // Type "c" - filters existing completions
14117    cx.simulate_keystroke("c");
14118    cx.run_until_parked();
14119    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14120    cx.assert_editor_state("obj.abcˇ");
14121    check_displayed_completions(vec!["abc"], &mut cx);
14122
14123    // Backspace to delete "c" - filters existing completions
14124    cx.update_editor(|editor, window, cx| {
14125        editor.backspace(&Backspace, window, cx);
14126    });
14127    cx.run_until_parked();
14128    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14129    cx.assert_editor_state("obj.abˇ");
14130    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14131
14132    // Moving cursor to the left dismisses menu.
14133    cx.update_editor(|editor, window, cx| {
14134        editor.move_left(&MoveLeft, window, cx);
14135    });
14136    cx.run_until_parked();
14137    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14138    cx.assert_editor_state("obj.aˇb");
14139    cx.update_editor(|editor, _, _| {
14140        assert_eq!(editor.context_menu_visible(), false);
14141    });
14142
14143    // Type "b" - new request
14144    cx.simulate_keystroke("b");
14145    let is_incomplete = false;
14146    handle_completion_request(
14147        "obj.<ab|>a",
14148        vec!["ab", "abc"],
14149        is_incomplete,
14150        counter.clone(),
14151        &mut cx,
14152    )
14153    .await;
14154    cx.run_until_parked();
14155    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14156    cx.assert_editor_state("obj.abˇb");
14157    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14158
14159    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14160    cx.update_editor(|editor, window, cx| {
14161        editor.backspace(&Backspace, window, cx);
14162    });
14163    let is_incomplete = false;
14164    handle_completion_request(
14165        "obj.<a|>b",
14166        vec!["a", "ab", "abc"],
14167        is_incomplete,
14168        counter.clone(),
14169        &mut cx,
14170    )
14171    .await;
14172    cx.run_until_parked();
14173    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14174    cx.assert_editor_state("obj.aˇb");
14175    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14176
14177    // Backspace to delete "a" - dismisses menu.
14178    cx.update_editor(|editor, window, cx| {
14179        editor.backspace(&Backspace, window, cx);
14180    });
14181    cx.run_until_parked();
14182    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14183    cx.assert_editor_state("obj.ˇb");
14184    cx.update_editor(|editor, _, _| {
14185        assert_eq!(editor.context_menu_visible(), false);
14186    });
14187}
14188
14189#[gpui::test]
14190async fn test_word_completion(cx: &mut TestAppContext) {
14191    let lsp_fetch_timeout_ms = 10;
14192    init_test(cx, |language_settings| {
14193        language_settings.defaults.completions = Some(CompletionSettingsContent {
14194            words_min_length: Some(0),
14195            lsp_fetch_timeout_ms: Some(10),
14196            lsp_insert_mode: Some(LspInsertMode::Insert),
14197            ..Default::default()
14198        });
14199    });
14200
14201    let mut cx = EditorLspTestContext::new_rust(
14202        lsp::ServerCapabilities {
14203            completion_provider: Some(lsp::CompletionOptions {
14204                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14205                ..lsp::CompletionOptions::default()
14206            }),
14207            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14208            ..lsp::ServerCapabilities::default()
14209        },
14210        cx,
14211    )
14212    .await;
14213
14214    let throttle_completions = Arc::new(AtomicBool::new(false));
14215
14216    let lsp_throttle_completions = throttle_completions.clone();
14217    let _completion_requests_handler =
14218        cx.lsp
14219            .server
14220            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14221                let lsp_throttle_completions = lsp_throttle_completions.clone();
14222                let cx = cx.clone();
14223                async move {
14224                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14225                        cx.background_executor()
14226                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14227                            .await;
14228                    }
14229                    Ok(Some(lsp::CompletionResponse::Array(vec![
14230                        lsp::CompletionItem {
14231                            label: "first".into(),
14232                            ..lsp::CompletionItem::default()
14233                        },
14234                        lsp::CompletionItem {
14235                            label: "last".into(),
14236                            ..lsp::CompletionItem::default()
14237                        },
14238                    ])))
14239                }
14240            });
14241
14242    cx.set_state(indoc! {"
14243        oneˇ
14244        two
14245        three
14246    "});
14247    cx.simulate_keystroke(".");
14248    cx.executor().run_until_parked();
14249    cx.condition(|editor, _| editor.context_menu_visible())
14250        .await;
14251    cx.update_editor(|editor, window, cx| {
14252        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14253        {
14254            assert_eq!(
14255                completion_menu_entries(menu),
14256                &["first", "last"],
14257                "When LSP server is fast to reply, no fallback word completions are used"
14258            );
14259        } else {
14260            panic!("expected completion menu to be open");
14261        }
14262        editor.cancel(&Cancel, window, cx);
14263    });
14264    cx.executor().run_until_parked();
14265    cx.condition(|editor, _| !editor.context_menu_visible())
14266        .await;
14267
14268    throttle_completions.store(true, atomic::Ordering::Release);
14269    cx.simulate_keystroke(".");
14270    cx.executor()
14271        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14272    cx.executor().run_until_parked();
14273    cx.condition(|editor, _| editor.context_menu_visible())
14274        .await;
14275    cx.update_editor(|editor, _, _| {
14276        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14277        {
14278            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14279                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14280        } else {
14281            panic!("expected completion menu to be open");
14282        }
14283    });
14284}
14285
14286#[gpui::test]
14287async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14288    init_test(cx, |language_settings| {
14289        language_settings.defaults.completions = Some(CompletionSettingsContent {
14290            words: Some(WordsCompletionMode::Enabled),
14291            words_min_length: Some(0),
14292            lsp_insert_mode: Some(LspInsertMode::Insert),
14293            ..Default::default()
14294        });
14295    });
14296
14297    let mut cx = EditorLspTestContext::new_rust(
14298        lsp::ServerCapabilities {
14299            completion_provider: Some(lsp::CompletionOptions {
14300                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14301                ..lsp::CompletionOptions::default()
14302            }),
14303            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14304            ..lsp::ServerCapabilities::default()
14305        },
14306        cx,
14307    )
14308    .await;
14309
14310    let _completion_requests_handler =
14311        cx.lsp
14312            .server
14313            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14314                Ok(Some(lsp::CompletionResponse::Array(vec![
14315                    lsp::CompletionItem {
14316                        label: "first".into(),
14317                        ..lsp::CompletionItem::default()
14318                    },
14319                    lsp::CompletionItem {
14320                        label: "last".into(),
14321                        ..lsp::CompletionItem::default()
14322                    },
14323                ])))
14324            });
14325
14326    cx.set_state(indoc! {"ˇ
14327        first
14328        last
14329        second
14330    "});
14331    cx.simulate_keystroke(".");
14332    cx.executor().run_until_parked();
14333    cx.condition(|editor, _| editor.context_menu_visible())
14334        .await;
14335    cx.update_editor(|editor, _, _| {
14336        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14337        {
14338            assert_eq!(
14339                completion_menu_entries(menu),
14340                &["first", "last", "second"],
14341                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14342            );
14343        } else {
14344            panic!("expected completion menu to be open");
14345        }
14346    });
14347}
14348
14349#[gpui::test]
14350async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14351    init_test(cx, |language_settings| {
14352        language_settings.defaults.completions = Some(CompletionSettingsContent {
14353            words: Some(WordsCompletionMode::Disabled),
14354            words_min_length: Some(0),
14355            lsp_insert_mode: Some(LspInsertMode::Insert),
14356            ..Default::default()
14357        });
14358    });
14359
14360    let mut cx = EditorLspTestContext::new_rust(
14361        lsp::ServerCapabilities {
14362            completion_provider: Some(lsp::CompletionOptions {
14363                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14364                ..lsp::CompletionOptions::default()
14365            }),
14366            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14367            ..lsp::ServerCapabilities::default()
14368        },
14369        cx,
14370    )
14371    .await;
14372
14373    let _completion_requests_handler =
14374        cx.lsp
14375            .server
14376            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14377                panic!("LSP completions should not be queried when dealing with word completions")
14378            });
14379
14380    cx.set_state(indoc! {"ˇ
14381        first
14382        last
14383        second
14384    "});
14385    cx.update_editor(|editor, window, cx| {
14386        editor.show_word_completions(&ShowWordCompletions, window, cx);
14387    });
14388    cx.executor().run_until_parked();
14389    cx.condition(|editor, _| editor.context_menu_visible())
14390        .await;
14391    cx.update_editor(|editor, _, _| {
14392        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14393        {
14394            assert_eq!(
14395                completion_menu_entries(menu),
14396                &["first", "last", "second"],
14397                "`ShowWordCompletions` action should show word completions"
14398            );
14399        } else {
14400            panic!("expected completion menu to be open");
14401        }
14402    });
14403
14404    cx.simulate_keystroke("l");
14405    cx.executor().run_until_parked();
14406    cx.condition(|editor, _| editor.context_menu_visible())
14407        .await;
14408    cx.update_editor(|editor, _, _| {
14409        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14410        {
14411            assert_eq!(
14412                completion_menu_entries(menu),
14413                &["last"],
14414                "After showing word completions, further editing should filter them and not query the LSP"
14415            );
14416        } else {
14417            panic!("expected completion menu to be open");
14418        }
14419    });
14420}
14421
14422#[gpui::test]
14423async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14424    init_test(cx, |language_settings| {
14425        language_settings.defaults.completions = Some(CompletionSettingsContent {
14426            words_min_length: Some(0),
14427            lsp: Some(false),
14428            lsp_insert_mode: Some(LspInsertMode::Insert),
14429            ..Default::default()
14430        });
14431    });
14432
14433    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14434
14435    cx.set_state(indoc! {"ˇ
14436        0_usize
14437        let
14438        33
14439        4.5f32
14440    "});
14441    cx.update_editor(|editor, window, cx| {
14442        editor.show_completions(&ShowCompletions::default(), window, cx);
14443    });
14444    cx.executor().run_until_parked();
14445    cx.condition(|editor, _| editor.context_menu_visible())
14446        .await;
14447    cx.update_editor(|editor, window, cx| {
14448        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14449        {
14450            assert_eq!(
14451                completion_menu_entries(menu),
14452                &["let"],
14453                "With no digits in the completion query, no digits should be in the word completions"
14454            );
14455        } else {
14456            panic!("expected completion menu to be open");
14457        }
14458        editor.cancel(&Cancel, window, cx);
14459    });
14460
14461    cx.set_state(indoc! {"14462        0_usize
14463        let
14464        3
14465        33.35f32
14466    "});
14467    cx.update_editor(|editor, window, cx| {
14468        editor.show_completions(&ShowCompletions::default(), window, cx);
14469    });
14470    cx.executor().run_until_parked();
14471    cx.condition(|editor, _| editor.context_menu_visible())
14472        .await;
14473    cx.update_editor(|editor, _, _| {
14474        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14475        {
14476            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14477                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14478        } else {
14479            panic!("expected completion menu to be open");
14480        }
14481    });
14482}
14483
14484#[gpui::test]
14485async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
14486    init_test(cx, |language_settings| {
14487        language_settings.defaults.completions = Some(CompletionSettingsContent {
14488            words: Some(WordsCompletionMode::Enabled),
14489            words_min_length: Some(3),
14490            lsp_insert_mode: Some(LspInsertMode::Insert),
14491            ..Default::default()
14492        });
14493    });
14494
14495    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14496    cx.set_state(indoc! {"ˇ
14497        wow
14498        wowen
14499        wowser
14500    "});
14501    cx.simulate_keystroke("w");
14502    cx.executor().run_until_parked();
14503    cx.update_editor(|editor, _, _| {
14504        if editor.context_menu.borrow_mut().is_some() {
14505            panic!(
14506                "expected completion menu to be hidden, as words completion threshold is not met"
14507            );
14508        }
14509    });
14510
14511    cx.update_editor(|editor, window, cx| {
14512        editor.show_word_completions(&ShowWordCompletions, window, cx);
14513    });
14514    cx.executor().run_until_parked();
14515    cx.update_editor(|editor, window, cx| {
14516        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14517        {
14518            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");
14519        } else {
14520            panic!("expected completion menu to be open after the word completions are called with an action");
14521        }
14522
14523        editor.cancel(&Cancel, window, cx);
14524    });
14525    cx.update_editor(|editor, _, _| {
14526        if editor.context_menu.borrow_mut().is_some() {
14527            panic!("expected completion menu to be hidden after canceling");
14528        }
14529    });
14530
14531    cx.simulate_keystroke("o");
14532    cx.executor().run_until_parked();
14533    cx.update_editor(|editor, _, _| {
14534        if editor.context_menu.borrow_mut().is_some() {
14535            panic!(
14536                "expected completion menu to be hidden, as words completion threshold is not met still"
14537            );
14538        }
14539    });
14540
14541    cx.simulate_keystroke("w");
14542    cx.executor().run_until_parked();
14543    cx.update_editor(|editor, _, _| {
14544        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14545        {
14546            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
14547        } else {
14548            panic!("expected completion menu to be open after the word completions threshold is met");
14549        }
14550    });
14551}
14552
14553#[gpui::test]
14554async fn test_word_completions_disabled(cx: &mut TestAppContext) {
14555    init_test(cx, |language_settings| {
14556        language_settings.defaults.completions = Some(CompletionSettingsContent {
14557            words: Some(WordsCompletionMode::Enabled),
14558            words_min_length: Some(0),
14559            lsp_insert_mode: Some(LspInsertMode::Insert),
14560            ..Default::default()
14561        });
14562    });
14563
14564    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14565    cx.update_editor(|editor, _, _| {
14566        editor.disable_word_completions();
14567    });
14568    cx.set_state(indoc! {"ˇ
14569        wow
14570        wowen
14571        wowser
14572    "});
14573    cx.simulate_keystroke("w");
14574    cx.executor().run_until_parked();
14575    cx.update_editor(|editor, _, _| {
14576        if editor.context_menu.borrow_mut().is_some() {
14577            panic!(
14578                "expected completion menu to be hidden, as words completion are disabled for this editor"
14579            );
14580        }
14581    });
14582
14583    cx.update_editor(|editor, window, cx| {
14584        editor.show_word_completions(&ShowWordCompletions, window, cx);
14585    });
14586    cx.executor().run_until_parked();
14587    cx.update_editor(|editor, _, _| {
14588        if editor.context_menu.borrow_mut().is_some() {
14589            panic!(
14590                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
14591            );
14592        }
14593    });
14594}
14595
14596fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
14597    let position = || lsp::Position {
14598        line: params.text_document_position.position.line,
14599        character: params.text_document_position.position.character,
14600    };
14601    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14602        range: lsp::Range {
14603            start: position(),
14604            end: position(),
14605        },
14606        new_text: text.to_string(),
14607    }))
14608}
14609
14610#[gpui::test]
14611async fn test_multiline_completion(cx: &mut TestAppContext) {
14612    init_test(cx, |_| {});
14613
14614    let fs = FakeFs::new(cx.executor());
14615    fs.insert_tree(
14616        path!("/a"),
14617        json!({
14618            "main.ts": "a",
14619        }),
14620    )
14621    .await;
14622
14623    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14624    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14625    let typescript_language = Arc::new(Language::new(
14626        LanguageConfig {
14627            name: "TypeScript".into(),
14628            matcher: LanguageMatcher {
14629                path_suffixes: vec!["ts".to_string()],
14630                ..LanguageMatcher::default()
14631            },
14632            line_comments: vec!["// ".into()],
14633            ..LanguageConfig::default()
14634        },
14635        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14636    ));
14637    language_registry.add(typescript_language.clone());
14638    let mut fake_servers = language_registry.register_fake_lsp(
14639        "TypeScript",
14640        FakeLspAdapter {
14641            capabilities: lsp::ServerCapabilities {
14642                completion_provider: Some(lsp::CompletionOptions {
14643                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14644                    ..lsp::CompletionOptions::default()
14645                }),
14646                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14647                ..lsp::ServerCapabilities::default()
14648            },
14649            // Emulate vtsls label generation
14650            label_for_completion: Some(Box::new(|item, _| {
14651                let text = if let Some(description) = item
14652                    .label_details
14653                    .as_ref()
14654                    .and_then(|label_details| label_details.description.as_ref())
14655                {
14656                    format!("{} {}", item.label, description)
14657                } else if let Some(detail) = &item.detail {
14658                    format!("{} {}", item.label, detail)
14659                } else {
14660                    item.label.clone()
14661                };
14662                let len = text.len();
14663                Some(language::CodeLabel {
14664                    text,
14665                    runs: Vec::new(),
14666                    filter_range: 0..len,
14667                })
14668            })),
14669            ..FakeLspAdapter::default()
14670        },
14671    );
14672    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14673    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14674    let worktree_id = workspace
14675        .update(cx, |workspace, _window, cx| {
14676            workspace.project().update(cx, |project, cx| {
14677                project.worktrees(cx).next().unwrap().read(cx).id()
14678            })
14679        })
14680        .unwrap();
14681    let _buffer = project
14682        .update(cx, |project, cx| {
14683            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
14684        })
14685        .await
14686        .unwrap();
14687    let editor = workspace
14688        .update(cx, |workspace, window, cx| {
14689            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
14690        })
14691        .unwrap()
14692        .await
14693        .unwrap()
14694        .downcast::<Editor>()
14695        .unwrap();
14696    let fake_server = fake_servers.next().await.unwrap();
14697
14698    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
14699    let multiline_label_2 = "a\nb\nc\n";
14700    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
14701    let multiline_description = "d\ne\nf\n";
14702    let multiline_detail_2 = "g\nh\ni\n";
14703
14704    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
14705        move |params, _| async move {
14706            Ok(Some(lsp::CompletionResponse::Array(vec![
14707                lsp::CompletionItem {
14708                    label: multiline_label.to_string(),
14709                    text_edit: gen_text_edit(&params, "new_text_1"),
14710                    ..lsp::CompletionItem::default()
14711                },
14712                lsp::CompletionItem {
14713                    label: "single line label 1".to_string(),
14714                    detail: Some(multiline_detail.to_string()),
14715                    text_edit: gen_text_edit(&params, "new_text_2"),
14716                    ..lsp::CompletionItem::default()
14717                },
14718                lsp::CompletionItem {
14719                    label: "single line label 2".to_string(),
14720                    label_details: Some(lsp::CompletionItemLabelDetails {
14721                        description: Some(multiline_description.to_string()),
14722                        detail: None,
14723                    }),
14724                    text_edit: gen_text_edit(&params, "new_text_2"),
14725                    ..lsp::CompletionItem::default()
14726                },
14727                lsp::CompletionItem {
14728                    label: multiline_label_2.to_string(),
14729                    detail: Some(multiline_detail_2.to_string()),
14730                    text_edit: gen_text_edit(&params, "new_text_3"),
14731                    ..lsp::CompletionItem::default()
14732                },
14733                lsp::CompletionItem {
14734                    label: "Label with many     spaces and \t but without newlines".to_string(),
14735                    detail: Some(
14736                        "Details with many     spaces and \t but without newlines".to_string(),
14737                    ),
14738                    text_edit: gen_text_edit(&params, "new_text_4"),
14739                    ..lsp::CompletionItem::default()
14740                },
14741            ])))
14742        },
14743    );
14744
14745    editor.update_in(cx, |editor, window, cx| {
14746        cx.focus_self(window);
14747        editor.move_to_end(&MoveToEnd, window, cx);
14748        editor.handle_input(".", window, cx);
14749    });
14750    cx.run_until_parked();
14751    completion_handle.next().await.unwrap();
14752
14753    editor.update(cx, |editor, _| {
14754        assert!(editor.context_menu_visible());
14755        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14756        {
14757            let completion_labels = menu
14758                .completions
14759                .borrow()
14760                .iter()
14761                .map(|c| c.label.text.clone())
14762                .collect::<Vec<_>>();
14763            assert_eq!(
14764                completion_labels,
14765                &[
14766                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
14767                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
14768                    "single line label 2 d e f ",
14769                    "a b c g h i ",
14770                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
14771                ],
14772                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
14773            );
14774
14775            for completion in menu
14776                .completions
14777                .borrow()
14778                .iter() {
14779                    assert_eq!(
14780                        completion.label.filter_range,
14781                        0..completion.label.text.len(),
14782                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
14783                    );
14784                }
14785        } else {
14786            panic!("expected completion menu to be open");
14787        }
14788    });
14789}
14790
14791#[gpui::test]
14792async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
14793    init_test(cx, |_| {});
14794    let mut cx = EditorLspTestContext::new_rust(
14795        lsp::ServerCapabilities {
14796            completion_provider: Some(lsp::CompletionOptions {
14797                trigger_characters: Some(vec![".".to_string()]),
14798                ..Default::default()
14799            }),
14800            ..Default::default()
14801        },
14802        cx,
14803    )
14804    .await;
14805    cx.lsp
14806        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14807            Ok(Some(lsp::CompletionResponse::Array(vec![
14808                lsp::CompletionItem {
14809                    label: "first".into(),
14810                    ..Default::default()
14811                },
14812                lsp::CompletionItem {
14813                    label: "last".into(),
14814                    ..Default::default()
14815                },
14816            ])))
14817        });
14818    cx.set_state("variableˇ");
14819    cx.simulate_keystroke(".");
14820    cx.executor().run_until_parked();
14821
14822    cx.update_editor(|editor, _, _| {
14823        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14824        {
14825            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
14826        } else {
14827            panic!("expected completion menu to be open");
14828        }
14829    });
14830
14831    cx.update_editor(|editor, window, cx| {
14832        editor.move_page_down(&MovePageDown::default(), window, cx);
14833        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14834        {
14835            assert!(
14836                menu.selected_item == 1,
14837                "expected PageDown to select the last item from the context menu"
14838            );
14839        } else {
14840            panic!("expected completion menu to stay open after PageDown");
14841        }
14842    });
14843
14844    cx.update_editor(|editor, window, cx| {
14845        editor.move_page_up(&MovePageUp::default(), window, cx);
14846        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14847        {
14848            assert!(
14849                menu.selected_item == 0,
14850                "expected PageUp to select the first item from the context menu"
14851            );
14852        } else {
14853            panic!("expected completion menu to stay open after PageUp");
14854        }
14855    });
14856}
14857
14858#[gpui::test]
14859async fn test_as_is_completions(cx: &mut TestAppContext) {
14860    init_test(cx, |_| {});
14861    let mut cx = EditorLspTestContext::new_rust(
14862        lsp::ServerCapabilities {
14863            completion_provider: Some(lsp::CompletionOptions {
14864                ..Default::default()
14865            }),
14866            ..Default::default()
14867        },
14868        cx,
14869    )
14870    .await;
14871    cx.lsp
14872        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14873            Ok(Some(lsp::CompletionResponse::Array(vec![
14874                lsp::CompletionItem {
14875                    label: "unsafe".into(),
14876                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14877                        range: lsp::Range {
14878                            start: lsp::Position {
14879                                line: 1,
14880                                character: 2,
14881                            },
14882                            end: lsp::Position {
14883                                line: 1,
14884                                character: 3,
14885                            },
14886                        },
14887                        new_text: "unsafe".to_string(),
14888                    })),
14889                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
14890                    ..Default::default()
14891                },
14892            ])))
14893        });
14894    cx.set_state("fn a() {}\n");
14895    cx.executor().run_until_parked();
14896    cx.update_editor(|editor, window, cx| {
14897        editor.show_completions(
14898            &ShowCompletions {
14899                trigger: Some("\n".into()),
14900            },
14901            window,
14902            cx,
14903        );
14904    });
14905    cx.executor().run_until_parked();
14906
14907    cx.update_editor(|editor, window, cx| {
14908        editor.confirm_completion(&Default::default(), window, cx)
14909    });
14910    cx.executor().run_until_parked();
14911    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
14912}
14913
14914#[gpui::test]
14915async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
14916    init_test(cx, |_| {});
14917    let language =
14918        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
14919    let mut cx = EditorLspTestContext::new(
14920        language,
14921        lsp::ServerCapabilities {
14922            completion_provider: Some(lsp::CompletionOptions {
14923                ..lsp::CompletionOptions::default()
14924            }),
14925            ..lsp::ServerCapabilities::default()
14926        },
14927        cx,
14928    )
14929    .await;
14930
14931    cx.set_state(
14932        "#ifndef BAR_H
14933#define BAR_H
14934
14935#include <stdbool.h>
14936
14937int fn_branch(bool do_branch1, bool do_branch2);
14938
14939#endif // BAR_H
14940ˇ",
14941    );
14942    cx.executor().run_until_parked();
14943    cx.update_editor(|editor, window, cx| {
14944        editor.handle_input("#", window, cx);
14945    });
14946    cx.executor().run_until_parked();
14947    cx.update_editor(|editor, window, cx| {
14948        editor.handle_input("i", window, cx);
14949    });
14950    cx.executor().run_until_parked();
14951    cx.update_editor(|editor, window, cx| {
14952        editor.handle_input("n", window, cx);
14953    });
14954    cx.executor().run_until_parked();
14955    cx.assert_editor_state(
14956        "#ifndef BAR_H
14957#define BAR_H
14958
14959#include <stdbool.h>
14960
14961int fn_branch(bool do_branch1, bool do_branch2);
14962
14963#endif // BAR_H
14964#inˇ",
14965    );
14966
14967    cx.lsp
14968        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14969            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
14970                is_incomplete: false,
14971                item_defaults: None,
14972                items: vec![lsp::CompletionItem {
14973                    kind: Some(lsp::CompletionItemKind::SNIPPET),
14974                    label_details: Some(lsp::CompletionItemLabelDetails {
14975                        detail: Some("header".to_string()),
14976                        description: None,
14977                    }),
14978                    label: " include".to_string(),
14979                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14980                        range: lsp::Range {
14981                            start: lsp::Position {
14982                                line: 8,
14983                                character: 1,
14984                            },
14985                            end: lsp::Position {
14986                                line: 8,
14987                                character: 1,
14988                            },
14989                        },
14990                        new_text: "include \"$0\"".to_string(),
14991                    })),
14992                    sort_text: Some("40b67681include".to_string()),
14993                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
14994                    filter_text: Some("include".to_string()),
14995                    insert_text: Some("include \"$0\"".to_string()),
14996                    ..lsp::CompletionItem::default()
14997                }],
14998            })))
14999        });
15000    cx.update_editor(|editor, window, cx| {
15001        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15002    });
15003    cx.executor().run_until_parked();
15004    cx.update_editor(|editor, window, cx| {
15005        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15006    });
15007    cx.executor().run_until_parked();
15008    cx.assert_editor_state(
15009        "#ifndef BAR_H
15010#define BAR_H
15011
15012#include <stdbool.h>
15013
15014int fn_branch(bool do_branch1, bool do_branch2);
15015
15016#endif // BAR_H
15017#include \"ˇ\"",
15018    );
15019
15020    cx.lsp
15021        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15022            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15023                is_incomplete: true,
15024                item_defaults: None,
15025                items: vec![lsp::CompletionItem {
15026                    kind: Some(lsp::CompletionItemKind::FILE),
15027                    label: "AGL/".to_string(),
15028                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15029                        range: lsp::Range {
15030                            start: lsp::Position {
15031                                line: 8,
15032                                character: 10,
15033                            },
15034                            end: lsp::Position {
15035                                line: 8,
15036                                character: 11,
15037                            },
15038                        },
15039                        new_text: "AGL/".to_string(),
15040                    })),
15041                    sort_text: Some("40b67681AGL/".to_string()),
15042                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15043                    filter_text: Some("AGL/".to_string()),
15044                    insert_text: Some("AGL/".to_string()),
15045                    ..lsp::CompletionItem::default()
15046                }],
15047            })))
15048        });
15049    cx.update_editor(|editor, window, cx| {
15050        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15051    });
15052    cx.executor().run_until_parked();
15053    cx.update_editor(|editor, window, cx| {
15054        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15055    });
15056    cx.executor().run_until_parked();
15057    cx.assert_editor_state(
15058        r##"#ifndef BAR_H
15059#define BAR_H
15060
15061#include <stdbool.h>
15062
15063int fn_branch(bool do_branch1, bool do_branch2);
15064
15065#endif // BAR_H
15066#include "AGL/ˇ"##,
15067    );
15068
15069    cx.update_editor(|editor, window, cx| {
15070        editor.handle_input("\"", window, cx);
15071    });
15072    cx.executor().run_until_parked();
15073    cx.assert_editor_state(
15074        r##"#ifndef BAR_H
15075#define BAR_H
15076
15077#include <stdbool.h>
15078
15079int fn_branch(bool do_branch1, bool do_branch2);
15080
15081#endif // BAR_H
15082#include "AGL/"ˇ"##,
15083    );
15084}
15085
15086#[gpui::test]
15087async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15088    init_test(cx, |_| {});
15089
15090    let mut cx = EditorLspTestContext::new_rust(
15091        lsp::ServerCapabilities {
15092            completion_provider: Some(lsp::CompletionOptions {
15093                trigger_characters: Some(vec![".".to_string()]),
15094                resolve_provider: Some(true),
15095                ..Default::default()
15096            }),
15097            ..Default::default()
15098        },
15099        cx,
15100    )
15101    .await;
15102
15103    cx.set_state("fn main() { let a = 2ˇ; }");
15104    cx.simulate_keystroke(".");
15105    let completion_item = lsp::CompletionItem {
15106        label: "Some".into(),
15107        kind: Some(lsp::CompletionItemKind::SNIPPET),
15108        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15109        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15110            kind: lsp::MarkupKind::Markdown,
15111            value: "```rust\nSome(2)\n```".to_string(),
15112        })),
15113        deprecated: Some(false),
15114        sort_text: Some("Some".to_string()),
15115        filter_text: Some("Some".to_string()),
15116        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15117        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15118            range: lsp::Range {
15119                start: lsp::Position {
15120                    line: 0,
15121                    character: 22,
15122                },
15123                end: lsp::Position {
15124                    line: 0,
15125                    character: 22,
15126                },
15127            },
15128            new_text: "Some(2)".to_string(),
15129        })),
15130        additional_text_edits: Some(vec![lsp::TextEdit {
15131            range: lsp::Range {
15132                start: lsp::Position {
15133                    line: 0,
15134                    character: 20,
15135                },
15136                end: lsp::Position {
15137                    line: 0,
15138                    character: 22,
15139                },
15140            },
15141            new_text: "".to_string(),
15142        }]),
15143        ..Default::default()
15144    };
15145
15146    let closure_completion_item = completion_item.clone();
15147    let counter = Arc::new(AtomicUsize::new(0));
15148    let counter_clone = counter.clone();
15149    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15150        let task_completion_item = closure_completion_item.clone();
15151        counter_clone.fetch_add(1, atomic::Ordering::Release);
15152        async move {
15153            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15154                is_incomplete: true,
15155                item_defaults: None,
15156                items: vec![task_completion_item],
15157            })))
15158        }
15159    });
15160
15161    cx.condition(|editor, _| editor.context_menu_visible())
15162        .await;
15163    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15164    assert!(request.next().await.is_some());
15165    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15166
15167    cx.simulate_keystrokes("S o m");
15168    cx.condition(|editor, _| editor.context_menu_visible())
15169        .await;
15170    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15171    assert!(request.next().await.is_some());
15172    assert!(request.next().await.is_some());
15173    assert!(request.next().await.is_some());
15174    request.close();
15175    assert!(request.next().await.is_none());
15176    assert_eq!(
15177        counter.load(atomic::Ordering::Acquire),
15178        4,
15179        "With the completions menu open, only one LSP request should happen per input"
15180    );
15181}
15182
15183#[gpui::test]
15184async fn test_toggle_comment(cx: &mut TestAppContext) {
15185    init_test(cx, |_| {});
15186    let mut cx = EditorTestContext::new(cx).await;
15187    let language = Arc::new(Language::new(
15188        LanguageConfig {
15189            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15190            ..Default::default()
15191        },
15192        Some(tree_sitter_rust::LANGUAGE.into()),
15193    ));
15194    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15195
15196    // If multiple selections intersect a line, the line is only toggled once.
15197    cx.set_state(indoc! {"
15198        fn a() {
15199            «//b();
15200            ˇ»// «c();
15201            //ˇ»  d();
15202        }
15203    "});
15204
15205    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15206
15207    cx.assert_editor_state(indoc! {"
15208        fn a() {
15209            «b();
15210            c();
15211            ˇ» d();
15212        }
15213    "});
15214
15215    // The comment prefix is inserted at the same column for every line in a
15216    // selection.
15217    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15218
15219    cx.assert_editor_state(indoc! {"
15220        fn a() {
15221            // «b();
15222            // c();
15223            ˇ»//  d();
15224        }
15225    "});
15226
15227    // If a selection ends at the beginning of a line, that line is not toggled.
15228    cx.set_selections_state(indoc! {"
15229        fn a() {
15230            // b();
15231            «// c();
15232        ˇ»    //  d();
15233        }
15234    "});
15235
15236    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15237
15238    cx.assert_editor_state(indoc! {"
15239        fn a() {
15240            // b();
15241            «c();
15242        ˇ»    //  d();
15243        }
15244    "});
15245
15246    // If a selection span a single line and is empty, the line is toggled.
15247    cx.set_state(indoc! {"
15248        fn a() {
15249            a();
15250            b();
15251        ˇ
15252        }
15253    "});
15254
15255    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15256
15257    cx.assert_editor_state(indoc! {"
15258        fn a() {
15259            a();
15260            b();
15261        //•ˇ
15262        }
15263    "});
15264
15265    // If a selection span multiple lines, empty lines are not toggled.
15266    cx.set_state(indoc! {"
15267        fn a() {
15268            «a();
15269
15270            c();ˇ»
15271        }
15272    "});
15273
15274    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15275
15276    cx.assert_editor_state(indoc! {"
15277        fn a() {
15278            // «a();
15279
15280            // c();ˇ»
15281        }
15282    "});
15283
15284    // If a selection includes multiple comment prefixes, all lines are uncommented.
15285    cx.set_state(indoc! {"
15286        fn a() {
15287            «// a();
15288            /// b();
15289            //! c();ˇ»
15290        }
15291    "});
15292
15293    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15294
15295    cx.assert_editor_state(indoc! {"
15296        fn a() {
15297            «a();
15298            b();
15299            c();ˇ»
15300        }
15301    "});
15302}
15303
15304#[gpui::test]
15305async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15306    init_test(cx, |_| {});
15307    let mut cx = EditorTestContext::new(cx).await;
15308    let language = Arc::new(Language::new(
15309        LanguageConfig {
15310            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15311            ..Default::default()
15312        },
15313        Some(tree_sitter_rust::LANGUAGE.into()),
15314    ));
15315    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15316
15317    let toggle_comments = &ToggleComments {
15318        advance_downwards: false,
15319        ignore_indent: true,
15320    };
15321
15322    // If multiple selections intersect a line, the line is only toggled once.
15323    cx.set_state(indoc! {"
15324        fn a() {
15325        //    «b();
15326        //    c();
15327        //    ˇ» d();
15328        }
15329    "});
15330
15331    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15332
15333    cx.assert_editor_state(indoc! {"
15334        fn a() {
15335            «b();
15336            c();
15337            ˇ» d();
15338        }
15339    "});
15340
15341    // The comment prefix is inserted at the beginning of each line
15342    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15343
15344    cx.assert_editor_state(indoc! {"
15345        fn a() {
15346        //    «b();
15347        //    c();
15348        //    ˇ» d();
15349        }
15350    "});
15351
15352    // If a selection ends at the beginning of a line, that line is not toggled.
15353    cx.set_selections_state(indoc! {"
15354        fn a() {
15355        //    b();
15356        //    «c();
15357        ˇ»//     d();
15358        }
15359    "});
15360
15361    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15362
15363    cx.assert_editor_state(indoc! {"
15364        fn a() {
15365        //    b();
15366            «c();
15367        ˇ»//     d();
15368        }
15369    "});
15370
15371    // If a selection span a single line and is empty, the line is toggled.
15372    cx.set_state(indoc! {"
15373        fn a() {
15374            a();
15375            b();
15376        ˇ
15377        }
15378    "});
15379
15380    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15381
15382    cx.assert_editor_state(indoc! {"
15383        fn a() {
15384            a();
15385            b();
15386        //ˇ
15387        }
15388    "});
15389
15390    // If a selection span multiple lines, empty lines are not toggled.
15391    cx.set_state(indoc! {"
15392        fn a() {
15393            «a();
15394
15395            c();ˇ»
15396        }
15397    "});
15398
15399    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15400
15401    cx.assert_editor_state(indoc! {"
15402        fn a() {
15403        //    «a();
15404
15405        //    c();ˇ»
15406        }
15407    "});
15408
15409    // If a selection includes multiple comment prefixes, all lines are uncommented.
15410    cx.set_state(indoc! {"
15411        fn a() {
15412        //    «a();
15413        ///    b();
15414        //!    c();ˇ»
15415        }
15416    "});
15417
15418    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15419
15420    cx.assert_editor_state(indoc! {"
15421        fn a() {
15422            «a();
15423            b();
15424            c();ˇ»
15425        }
15426    "});
15427}
15428
15429#[gpui::test]
15430async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15431    init_test(cx, |_| {});
15432
15433    let language = Arc::new(Language::new(
15434        LanguageConfig {
15435            line_comments: vec!["// ".into()],
15436            ..Default::default()
15437        },
15438        Some(tree_sitter_rust::LANGUAGE.into()),
15439    ));
15440
15441    let mut cx = EditorTestContext::new(cx).await;
15442
15443    cx.language_registry().add(language.clone());
15444    cx.update_buffer(|buffer, cx| {
15445        buffer.set_language(Some(language), cx);
15446    });
15447
15448    let toggle_comments = &ToggleComments {
15449        advance_downwards: true,
15450        ignore_indent: false,
15451    };
15452
15453    // Single cursor on one line -> advance
15454    // Cursor moves horizontally 3 characters as well on non-blank line
15455    cx.set_state(indoc!(
15456        "fn a() {
15457             ˇdog();
15458             cat();
15459        }"
15460    ));
15461    cx.update_editor(|editor, window, cx| {
15462        editor.toggle_comments(toggle_comments, window, cx);
15463    });
15464    cx.assert_editor_state(indoc!(
15465        "fn a() {
15466             // dog();
15467             catˇ();
15468        }"
15469    ));
15470
15471    // Single selection on one line -> don't advance
15472    cx.set_state(indoc!(
15473        "fn a() {
15474             «dog()ˇ»;
15475             cat();
15476        }"
15477    ));
15478    cx.update_editor(|editor, window, cx| {
15479        editor.toggle_comments(toggle_comments, window, cx);
15480    });
15481    cx.assert_editor_state(indoc!(
15482        "fn a() {
15483             // «dog()ˇ»;
15484             cat();
15485        }"
15486    ));
15487
15488    // Multiple cursors on one line -> advance
15489    cx.set_state(indoc!(
15490        "fn a() {
15491             ˇdˇog();
15492             cat();
15493        }"
15494    ));
15495    cx.update_editor(|editor, window, cx| {
15496        editor.toggle_comments(toggle_comments, window, cx);
15497    });
15498    cx.assert_editor_state(indoc!(
15499        "fn a() {
15500             // dog();
15501             catˇ(ˇ);
15502        }"
15503    ));
15504
15505    // Multiple cursors on one line, with selection -> don't advance
15506    cx.set_state(indoc!(
15507        "fn a() {
15508             ˇdˇog«()ˇ»;
15509             cat();
15510        }"
15511    ));
15512    cx.update_editor(|editor, window, cx| {
15513        editor.toggle_comments(toggle_comments, window, cx);
15514    });
15515    cx.assert_editor_state(indoc!(
15516        "fn a() {
15517             // ˇdˇog«()ˇ»;
15518             cat();
15519        }"
15520    ));
15521
15522    // Single cursor on one line -> advance
15523    // Cursor moves to column 0 on blank line
15524    cx.set_state(indoc!(
15525        "fn a() {
15526             ˇdog();
15527
15528             cat();
15529        }"
15530    ));
15531    cx.update_editor(|editor, window, cx| {
15532        editor.toggle_comments(toggle_comments, window, cx);
15533    });
15534    cx.assert_editor_state(indoc!(
15535        "fn a() {
15536             // dog();
15537        ˇ
15538             cat();
15539        }"
15540    ));
15541
15542    // Single cursor on one line -> advance
15543    // Cursor starts and ends at column 0
15544    cx.set_state(indoc!(
15545        "fn a() {
15546         ˇ    dog();
15547             cat();
15548        }"
15549    ));
15550    cx.update_editor(|editor, window, cx| {
15551        editor.toggle_comments(toggle_comments, window, cx);
15552    });
15553    cx.assert_editor_state(indoc!(
15554        "fn a() {
15555             // dog();
15556         ˇ    cat();
15557        }"
15558    ));
15559}
15560
15561#[gpui::test]
15562async fn test_toggle_block_comment(cx: &mut TestAppContext) {
15563    init_test(cx, |_| {});
15564
15565    let mut cx = EditorTestContext::new(cx).await;
15566
15567    let html_language = Arc::new(
15568        Language::new(
15569            LanguageConfig {
15570                name: "HTML".into(),
15571                block_comment: Some(BlockCommentConfig {
15572                    start: "<!-- ".into(),
15573                    prefix: "".into(),
15574                    end: " -->".into(),
15575                    tab_size: 0,
15576                }),
15577                ..Default::default()
15578            },
15579            Some(tree_sitter_html::LANGUAGE.into()),
15580        )
15581        .with_injection_query(
15582            r#"
15583            (script_element
15584                (raw_text) @injection.content
15585                (#set! injection.language "javascript"))
15586            "#,
15587        )
15588        .unwrap(),
15589    );
15590
15591    let javascript_language = Arc::new(Language::new(
15592        LanguageConfig {
15593            name: "JavaScript".into(),
15594            line_comments: vec!["// ".into()],
15595            ..Default::default()
15596        },
15597        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15598    ));
15599
15600    cx.language_registry().add(html_language.clone());
15601    cx.language_registry().add(javascript_language);
15602    cx.update_buffer(|buffer, cx| {
15603        buffer.set_language(Some(html_language), cx);
15604    });
15605
15606    // Toggle comments for empty selections
15607    cx.set_state(
15608        &r#"
15609            <p>A</p>ˇ
15610            <p>B</p>ˇ
15611            <p>C</p>ˇ
15612        "#
15613        .unindent(),
15614    );
15615    cx.update_editor(|editor, window, cx| {
15616        editor.toggle_comments(&ToggleComments::default(), window, cx)
15617    });
15618    cx.assert_editor_state(
15619        &r#"
15620            <!-- <p>A</p>ˇ -->
15621            <!-- <p>B</p>ˇ -->
15622            <!-- <p>C</p>ˇ -->
15623        "#
15624        .unindent(),
15625    );
15626    cx.update_editor(|editor, window, cx| {
15627        editor.toggle_comments(&ToggleComments::default(), window, cx)
15628    });
15629    cx.assert_editor_state(
15630        &r#"
15631            <p>A</p>ˇ
15632            <p>B</p>ˇ
15633            <p>C</p>ˇ
15634        "#
15635        .unindent(),
15636    );
15637
15638    // Toggle comments for mixture of empty and non-empty selections, where
15639    // multiple selections occupy a given line.
15640    cx.set_state(
15641        &r#"
15642            <p>A«</p>
15643            <p>ˇ»B</p>ˇ
15644            <p>C«</p>
15645            <p>ˇ»D</p>ˇ
15646        "#
15647        .unindent(),
15648    );
15649
15650    cx.update_editor(|editor, window, cx| {
15651        editor.toggle_comments(&ToggleComments::default(), window, cx)
15652    });
15653    cx.assert_editor_state(
15654        &r#"
15655            <!-- <p>A«</p>
15656            <p>ˇ»B</p>ˇ -->
15657            <!-- <p>C«</p>
15658            <p>ˇ»D</p>ˇ -->
15659        "#
15660        .unindent(),
15661    );
15662    cx.update_editor(|editor, window, cx| {
15663        editor.toggle_comments(&ToggleComments::default(), window, cx)
15664    });
15665    cx.assert_editor_state(
15666        &r#"
15667            <p>A«</p>
15668            <p>ˇ»B</p>ˇ
15669            <p>C«</p>
15670            <p>ˇ»D</p>ˇ
15671        "#
15672        .unindent(),
15673    );
15674
15675    // Toggle comments when different languages are active for different
15676    // selections.
15677    cx.set_state(
15678        &r#"
15679            ˇ<script>
15680                ˇvar x = new Y();
15681            ˇ</script>
15682        "#
15683        .unindent(),
15684    );
15685    cx.executor().run_until_parked();
15686    cx.update_editor(|editor, window, cx| {
15687        editor.toggle_comments(&ToggleComments::default(), window, cx)
15688    });
15689    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
15690    // Uncommenting and commenting from this position brings in even more wrong artifacts.
15691    cx.assert_editor_state(
15692        &r#"
15693            <!-- ˇ<script> -->
15694                // ˇvar x = new Y();
15695            <!-- ˇ</script> -->
15696        "#
15697        .unindent(),
15698    );
15699}
15700
15701#[gpui::test]
15702fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
15703    init_test(cx, |_| {});
15704
15705    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15706    let multibuffer = cx.new(|cx| {
15707        let mut multibuffer = MultiBuffer::new(ReadWrite);
15708        multibuffer.push_excerpts(
15709            buffer.clone(),
15710            [
15711                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
15712                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
15713            ],
15714            cx,
15715        );
15716        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
15717        multibuffer
15718    });
15719
15720    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15721    editor.update_in(cx, |editor, window, cx| {
15722        assert_eq!(editor.text(cx), "aaaa\nbbbb");
15723        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15724            s.select_ranges([
15725                Point::new(0, 0)..Point::new(0, 0),
15726                Point::new(1, 0)..Point::new(1, 0),
15727            ])
15728        });
15729
15730        editor.handle_input("X", window, cx);
15731        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
15732        assert_eq!(
15733            editor.selections.ranges(cx),
15734            [
15735                Point::new(0, 1)..Point::new(0, 1),
15736                Point::new(1, 1)..Point::new(1, 1),
15737            ]
15738        );
15739
15740        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
15741        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15742            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
15743        });
15744        editor.backspace(&Default::default(), window, cx);
15745        assert_eq!(editor.text(cx), "Xa\nbbb");
15746        assert_eq!(
15747            editor.selections.ranges(cx),
15748            [Point::new(1, 0)..Point::new(1, 0)]
15749        );
15750
15751        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15752            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
15753        });
15754        editor.backspace(&Default::default(), window, cx);
15755        assert_eq!(editor.text(cx), "X\nbb");
15756        assert_eq!(
15757            editor.selections.ranges(cx),
15758            [Point::new(0, 1)..Point::new(0, 1)]
15759        );
15760    });
15761}
15762
15763#[gpui::test]
15764fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
15765    init_test(cx, |_| {});
15766
15767    let markers = vec![('[', ']').into(), ('(', ')').into()];
15768    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
15769        indoc! {"
15770            [aaaa
15771            (bbbb]
15772            cccc)",
15773        },
15774        markers.clone(),
15775    );
15776    let excerpt_ranges = markers.into_iter().map(|marker| {
15777        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
15778        ExcerptRange::new(context)
15779    });
15780    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
15781    let multibuffer = cx.new(|cx| {
15782        let mut multibuffer = MultiBuffer::new(ReadWrite);
15783        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
15784        multibuffer
15785    });
15786
15787    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15788    editor.update_in(cx, |editor, window, cx| {
15789        let (expected_text, selection_ranges) = marked_text_ranges(
15790            indoc! {"
15791                aaaa
15792                bˇbbb
15793                bˇbbˇb
15794                cccc"
15795            },
15796            true,
15797        );
15798        assert_eq!(editor.text(cx), expected_text);
15799        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15800            s.select_ranges(selection_ranges)
15801        });
15802
15803        editor.handle_input("X", window, cx);
15804
15805        let (expected_text, expected_selections) = marked_text_ranges(
15806            indoc! {"
15807                aaaa
15808                bXˇbbXb
15809                bXˇbbXˇb
15810                cccc"
15811            },
15812            false,
15813        );
15814        assert_eq!(editor.text(cx), expected_text);
15815        assert_eq!(editor.selections.ranges(cx), expected_selections);
15816
15817        editor.newline(&Newline, window, cx);
15818        let (expected_text, expected_selections) = marked_text_ranges(
15819            indoc! {"
15820                aaaa
15821                bX
15822                ˇbbX
15823                b
15824                bX
15825                ˇbbX
15826                ˇb
15827                cccc"
15828            },
15829            false,
15830        );
15831        assert_eq!(editor.text(cx), expected_text);
15832        assert_eq!(editor.selections.ranges(cx), expected_selections);
15833    });
15834}
15835
15836#[gpui::test]
15837fn test_refresh_selections(cx: &mut TestAppContext) {
15838    init_test(cx, |_| {});
15839
15840    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15841    let mut excerpt1_id = None;
15842    let multibuffer = cx.new(|cx| {
15843        let mut multibuffer = MultiBuffer::new(ReadWrite);
15844        excerpt1_id = multibuffer
15845            .push_excerpts(
15846                buffer.clone(),
15847                [
15848                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15849                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15850                ],
15851                cx,
15852            )
15853            .into_iter()
15854            .next();
15855        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15856        multibuffer
15857    });
15858
15859    let editor = cx.add_window(|window, cx| {
15860        let mut editor = build_editor(multibuffer.clone(), window, cx);
15861        let snapshot = editor.snapshot(window, cx);
15862        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15863            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
15864        });
15865        editor.begin_selection(
15866            Point::new(2, 1).to_display_point(&snapshot),
15867            true,
15868            1,
15869            window,
15870            cx,
15871        );
15872        assert_eq!(
15873            editor.selections.ranges(cx),
15874            [
15875                Point::new(1, 3)..Point::new(1, 3),
15876                Point::new(2, 1)..Point::new(2, 1),
15877            ]
15878        );
15879        editor
15880    });
15881
15882    // Refreshing selections is a no-op when excerpts haven't changed.
15883    _ = editor.update(cx, |editor, window, cx| {
15884        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15885        assert_eq!(
15886            editor.selections.ranges(cx),
15887            [
15888                Point::new(1, 3)..Point::new(1, 3),
15889                Point::new(2, 1)..Point::new(2, 1),
15890            ]
15891        );
15892    });
15893
15894    multibuffer.update(cx, |multibuffer, cx| {
15895        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
15896    });
15897    _ = editor.update(cx, |editor, window, cx| {
15898        // Removing an excerpt causes the first selection to become degenerate.
15899        assert_eq!(
15900            editor.selections.ranges(cx),
15901            [
15902                Point::new(0, 0)..Point::new(0, 0),
15903                Point::new(0, 1)..Point::new(0, 1)
15904            ]
15905        );
15906
15907        // Refreshing selections will relocate the first selection to the original buffer
15908        // location.
15909        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15910        assert_eq!(
15911            editor.selections.ranges(cx),
15912            [
15913                Point::new(0, 1)..Point::new(0, 1),
15914                Point::new(0, 3)..Point::new(0, 3)
15915            ]
15916        );
15917        assert!(editor.selections.pending_anchor().is_some());
15918    });
15919}
15920
15921#[gpui::test]
15922fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
15923    init_test(cx, |_| {});
15924
15925    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15926    let mut excerpt1_id = None;
15927    let multibuffer = cx.new(|cx| {
15928        let mut multibuffer = MultiBuffer::new(ReadWrite);
15929        excerpt1_id = multibuffer
15930            .push_excerpts(
15931                buffer.clone(),
15932                [
15933                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15934                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15935                ],
15936                cx,
15937            )
15938            .into_iter()
15939            .next();
15940        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15941        multibuffer
15942    });
15943
15944    let editor = cx.add_window(|window, cx| {
15945        let mut editor = build_editor(multibuffer.clone(), window, cx);
15946        let snapshot = editor.snapshot(window, cx);
15947        editor.begin_selection(
15948            Point::new(1, 3).to_display_point(&snapshot),
15949            false,
15950            1,
15951            window,
15952            cx,
15953        );
15954        assert_eq!(
15955            editor.selections.ranges(cx),
15956            [Point::new(1, 3)..Point::new(1, 3)]
15957        );
15958        editor
15959    });
15960
15961    multibuffer.update(cx, |multibuffer, cx| {
15962        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
15963    });
15964    _ = editor.update(cx, |editor, window, cx| {
15965        assert_eq!(
15966            editor.selections.ranges(cx),
15967            [Point::new(0, 0)..Point::new(0, 0)]
15968        );
15969
15970        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
15971        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15972        assert_eq!(
15973            editor.selections.ranges(cx),
15974            [Point::new(0, 3)..Point::new(0, 3)]
15975        );
15976        assert!(editor.selections.pending_anchor().is_some());
15977    });
15978}
15979
15980#[gpui::test]
15981async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
15982    init_test(cx, |_| {});
15983
15984    let language = Arc::new(
15985        Language::new(
15986            LanguageConfig {
15987                brackets: BracketPairConfig {
15988                    pairs: vec![
15989                        BracketPair {
15990                            start: "{".to_string(),
15991                            end: "}".to_string(),
15992                            close: true,
15993                            surround: true,
15994                            newline: true,
15995                        },
15996                        BracketPair {
15997                            start: "/* ".to_string(),
15998                            end: " */".to_string(),
15999                            close: true,
16000                            surround: true,
16001                            newline: true,
16002                        },
16003                    ],
16004                    ..Default::default()
16005                },
16006                ..Default::default()
16007            },
16008            Some(tree_sitter_rust::LANGUAGE.into()),
16009        )
16010        .with_indents_query("")
16011        .unwrap(),
16012    );
16013
16014    let text = concat!(
16015        "{   }\n",     //
16016        "  x\n",       //
16017        "  /*   */\n", //
16018        "x\n",         //
16019        "{{} }\n",     //
16020    );
16021
16022    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16023    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16024    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16025    editor
16026        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16027        .await;
16028
16029    editor.update_in(cx, |editor, window, cx| {
16030        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16031            s.select_display_ranges([
16032                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16033                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16034                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16035            ])
16036        });
16037        editor.newline(&Newline, window, cx);
16038
16039        assert_eq!(
16040            editor.buffer().read(cx).read(cx).text(),
16041            concat!(
16042                "{ \n",    // Suppress rustfmt
16043                "\n",      //
16044                "}\n",     //
16045                "  x\n",   //
16046                "  /* \n", //
16047                "  \n",    //
16048                "  */\n",  //
16049                "x\n",     //
16050                "{{} \n",  //
16051                "}\n",     //
16052            )
16053        );
16054    });
16055}
16056
16057#[gpui::test]
16058fn test_highlighted_ranges(cx: &mut TestAppContext) {
16059    init_test(cx, |_| {});
16060
16061    let editor = cx.add_window(|window, cx| {
16062        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16063        build_editor(buffer, window, cx)
16064    });
16065
16066    _ = editor.update(cx, |editor, window, cx| {
16067        struct Type1;
16068        struct Type2;
16069
16070        let buffer = editor.buffer.read(cx).snapshot(cx);
16071
16072        let anchor_range =
16073            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16074
16075        editor.highlight_background::<Type1>(
16076            &[
16077                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16078                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16079                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16080                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16081            ],
16082            |_| Hsla::red(),
16083            cx,
16084        );
16085        editor.highlight_background::<Type2>(
16086            &[
16087                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16088                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16089                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16090                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16091            ],
16092            |_| Hsla::green(),
16093            cx,
16094        );
16095
16096        let snapshot = editor.snapshot(window, cx);
16097        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16098            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16099            &snapshot,
16100            cx.theme(),
16101        );
16102        assert_eq!(
16103            highlighted_ranges,
16104            &[
16105                (
16106                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16107                    Hsla::green(),
16108                ),
16109                (
16110                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16111                    Hsla::red(),
16112                ),
16113                (
16114                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16115                    Hsla::green(),
16116                ),
16117                (
16118                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16119                    Hsla::red(),
16120                ),
16121            ]
16122        );
16123        assert_eq!(
16124            editor.sorted_background_highlights_in_range(
16125                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16126                &snapshot,
16127                cx.theme(),
16128            ),
16129            &[(
16130                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16131                Hsla::red(),
16132            )]
16133        );
16134    });
16135}
16136
16137#[gpui::test]
16138async fn test_following(cx: &mut TestAppContext) {
16139    init_test(cx, |_| {});
16140
16141    let fs = FakeFs::new(cx.executor());
16142    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16143
16144    let buffer = project.update(cx, |project, cx| {
16145        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16146        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16147    });
16148    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16149    let follower = cx.update(|cx| {
16150        cx.open_window(
16151            WindowOptions {
16152                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16153                    gpui::Point::new(px(0.), px(0.)),
16154                    gpui::Point::new(px(10.), px(80.)),
16155                ))),
16156                ..Default::default()
16157            },
16158            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16159        )
16160        .unwrap()
16161    });
16162
16163    let is_still_following = Rc::new(RefCell::new(true));
16164    let follower_edit_event_count = Rc::new(RefCell::new(0));
16165    let pending_update = Rc::new(RefCell::new(None));
16166    let leader_entity = leader.root(cx).unwrap();
16167    let follower_entity = follower.root(cx).unwrap();
16168    _ = follower.update(cx, {
16169        let update = pending_update.clone();
16170        let is_still_following = is_still_following.clone();
16171        let follower_edit_event_count = follower_edit_event_count.clone();
16172        |_, window, cx| {
16173            cx.subscribe_in(
16174                &leader_entity,
16175                window,
16176                move |_, leader, event, window, cx| {
16177                    leader.read(cx).add_event_to_update_proto(
16178                        event,
16179                        &mut update.borrow_mut(),
16180                        window,
16181                        cx,
16182                    );
16183                },
16184            )
16185            .detach();
16186
16187            cx.subscribe_in(
16188                &follower_entity,
16189                window,
16190                move |_, _, event: &EditorEvent, _window, _cx| {
16191                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16192                        *is_still_following.borrow_mut() = false;
16193                    }
16194
16195                    if let EditorEvent::BufferEdited = event {
16196                        *follower_edit_event_count.borrow_mut() += 1;
16197                    }
16198                },
16199            )
16200            .detach();
16201        }
16202    });
16203
16204    // Update the selections only
16205    _ = leader.update(cx, |leader, window, cx| {
16206        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16207            s.select_ranges([1..1])
16208        });
16209    });
16210    follower
16211        .update(cx, |follower, window, cx| {
16212            follower.apply_update_proto(
16213                &project,
16214                pending_update.borrow_mut().take().unwrap(),
16215                window,
16216                cx,
16217            )
16218        })
16219        .unwrap()
16220        .await
16221        .unwrap();
16222    _ = follower.update(cx, |follower, _, cx| {
16223        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
16224    });
16225    assert!(*is_still_following.borrow());
16226    assert_eq!(*follower_edit_event_count.borrow(), 0);
16227
16228    // Update the scroll position only
16229    _ = leader.update(cx, |leader, window, cx| {
16230        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16231    });
16232    follower
16233        .update(cx, |follower, window, cx| {
16234            follower.apply_update_proto(
16235                &project,
16236                pending_update.borrow_mut().take().unwrap(),
16237                window,
16238                cx,
16239            )
16240        })
16241        .unwrap()
16242        .await
16243        .unwrap();
16244    assert_eq!(
16245        follower
16246            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16247            .unwrap(),
16248        gpui::Point::new(1.5, 3.5)
16249    );
16250    assert!(*is_still_following.borrow());
16251    assert_eq!(*follower_edit_event_count.borrow(), 0);
16252
16253    // Update the selections and scroll position. The follower's scroll position is updated
16254    // via autoscroll, not via the leader's exact scroll position.
16255    _ = leader.update(cx, |leader, window, cx| {
16256        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16257            s.select_ranges([0..0])
16258        });
16259        leader.request_autoscroll(Autoscroll::newest(), cx);
16260        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16261    });
16262    follower
16263        .update(cx, |follower, window, cx| {
16264            follower.apply_update_proto(
16265                &project,
16266                pending_update.borrow_mut().take().unwrap(),
16267                window,
16268                cx,
16269            )
16270        })
16271        .unwrap()
16272        .await
16273        .unwrap();
16274    _ = follower.update(cx, |follower, _, cx| {
16275        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16276        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
16277    });
16278    assert!(*is_still_following.borrow());
16279
16280    // Creating a pending selection that precedes another selection
16281    _ = leader.update(cx, |leader, window, cx| {
16282        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16283            s.select_ranges([1..1])
16284        });
16285        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16286    });
16287    follower
16288        .update(cx, |follower, window, cx| {
16289            follower.apply_update_proto(
16290                &project,
16291                pending_update.borrow_mut().take().unwrap(),
16292                window,
16293                cx,
16294            )
16295        })
16296        .unwrap()
16297        .await
16298        .unwrap();
16299    _ = follower.update(cx, |follower, _, cx| {
16300        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
16301    });
16302    assert!(*is_still_following.borrow());
16303
16304    // Extend the pending selection so that it surrounds another selection
16305    _ = leader.update(cx, |leader, window, cx| {
16306        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16307    });
16308    follower
16309        .update(cx, |follower, window, cx| {
16310            follower.apply_update_proto(
16311                &project,
16312                pending_update.borrow_mut().take().unwrap(),
16313                window,
16314                cx,
16315            )
16316        })
16317        .unwrap()
16318        .await
16319        .unwrap();
16320    _ = follower.update(cx, |follower, _, cx| {
16321        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
16322    });
16323
16324    // Scrolling locally breaks the follow
16325    _ = follower.update(cx, |follower, window, cx| {
16326        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16327        follower.set_scroll_anchor(
16328            ScrollAnchor {
16329                anchor: top_anchor,
16330                offset: gpui::Point::new(0.0, 0.5),
16331            },
16332            window,
16333            cx,
16334        );
16335    });
16336    assert!(!(*is_still_following.borrow()));
16337}
16338
16339#[gpui::test]
16340async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16341    init_test(cx, |_| {});
16342
16343    let fs = FakeFs::new(cx.executor());
16344    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16345    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16346    let pane = workspace
16347        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16348        .unwrap();
16349
16350    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16351
16352    let leader = pane.update_in(cx, |_, window, cx| {
16353        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16354        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16355    });
16356
16357    // Start following the editor when it has no excerpts.
16358    let mut state_message =
16359        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16360    let workspace_entity = workspace.root(cx).unwrap();
16361    let follower_1 = cx
16362        .update_window(*workspace.deref(), |_, window, cx| {
16363            Editor::from_state_proto(
16364                workspace_entity,
16365                ViewId {
16366                    creator: CollaboratorId::PeerId(PeerId::default()),
16367                    id: 0,
16368                },
16369                &mut state_message,
16370                window,
16371                cx,
16372            )
16373        })
16374        .unwrap()
16375        .unwrap()
16376        .await
16377        .unwrap();
16378
16379    let update_message = Rc::new(RefCell::new(None));
16380    follower_1.update_in(cx, {
16381        let update = update_message.clone();
16382        |_, window, cx| {
16383            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16384                leader.read(cx).add_event_to_update_proto(
16385                    event,
16386                    &mut update.borrow_mut(),
16387                    window,
16388                    cx,
16389                );
16390            })
16391            .detach();
16392        }
16393    });
16394
16395    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16396        (
16397            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16398            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16399        )
16400    });
16401
16402    // Insert some excerpts.
16403    leader.update(cx, |leader, cx| {
16404        leader.buffer.update(cx, |multibuffer, cx| {
16405            multibuffer.set_excerpts_for_path(
16406                PathKey::namespaced(1, "b.txt".into()),
16407                buffer_1.clone(),
16408                vec![
16409                    Point::row_range(0..3),
16410                    Point::row_range(1..6),
16411                    Point::row_range(12..15),
16412                ],
16413                0,
16414                cx,
16415            );
16416            multibuffer.set_excerpts_for_path(
16417                PathKey::namespaced(1, "a.txt".into()),
16418                buffer_2.clone(),
16419                vec![Point::row_range(0..6), Point::row_range(8..12)],
16420                0,
16421                cx,
16422            );
16423        });
16424    });
16425
16426    // Apply the update of adding the excerpts.
16427    follower_1
16428        .update_in(cx, |follower, window, cx| {
16429            follower.apply_update_proto(
16430                &project,
16431                update_message.borrow().clone().unwrap(),
16432                window,
16433                cx,
16434            )
16435        })
16436        .await
16437        .unwrap();
16438    assert_eq!(
16439        follower_1.update(cx, |editor, cx| editor.text(cx)),
16440        leader.update(cx, |editor, cx| editor.text(cx))
16441    );
16442    update_message.borrow_mut().take();
16443
16444    // Start following separately after it already has excerpts.
16445    let mut state_message =
16446        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16447    let workspace_entity = workspace.root(cx).unwrap();
16448    let follower_2 = cx
16449        .update_window(*workspace.deref(), |_, window, cx| {
16450            Editor::from_state_proto(
16451                workspace_entity,
16452                ViewId {
16453                    creator: CollaboratorId::PeerId(PeerId::default()),
16454                    id: 0,
16455                },
16456                &mut state_message,
16457                window,
16458                cx,
16459            )
16460        })
16461        .unwrap()
16462        .unwrap()
16463        .await
16464        .unwrap();
16465    assert_eq!(
16466        follower_2.update(cx, |editor, cx| editor.text(cx)),
16467        leader.update(cx, |editor, cx| editor.text(cx))
16468    );
16469
16470    // Remove some excerpts.
16471    leader.update(cx, |leader, cx| {
16472        leader.buffer.update(cx, |multibuffer, cx| {
16473            let excerpt_ids = multibuffer.excerpt_ids();
16474            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16475            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16476        });
16477    });
16478
16479    // Apply the update of removing the excerpts.
16480    follower_1
16481        .update_in(cx, |follower, window, cx| {
16482            follower.apply_update_proto(
16483                &project,
16484                update_message.borrow().clone().unwrap(),
16485                window,
16486                cx,
16487            )
16488        })
16489        .await
16490        .unwrap();
16491    follower_2
16492        .update_in(cx, |follower, window, cx| {
16493            follower.apply_update_proto(
16494                &project,
16495                update_message.borrow().clone().unwrap(),
16496                window,
16497                cx,
16498            )
16499        })
16500        .await
16501        .unwrap();
16502    update_message.borrow_mut().take();
16503    assert_eq!(
16504        follower_1.update(cx, |editor, cx| editor.text(cx)),
16505        leader.update(cx, |editor, cx| editor.text(cx))
16506    );
16507}
16508
16509#[gpui::test]
16510async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16511    init_test(cx, |_| {});
16512
16513    let mut cx = EditorTestContext::new(cx).await;
16514    let lsp_store =
16515        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
16516
16517    cx.set_state(indoc! {"
16518        ˇfn func(abc def: i32) -> u32 {
16519        }
16520    "});
16521
16522    cx.update(|_, cx| {
16523        lsp_store.update(cx, |lsp_store, cx| {
16524            lsp_store
16525                .update_diagnostics(
16526                    LanguageServerId(0),
16527                    lsp::PublishDiagnosticsParams {
16528                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
16529                        version: None,
16530                        diagnostics: vec![
16531                            lsp::Diagnostic {
16532                                range: lsp::Range::new(
16533                                    lsp::Position::new(0, 11),
16534                                    lsp::Position::new(0, 12),
16535                                ),
16536                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16537                                ..Default::default()
16538                            },
16539                            lsp::Diagnostic {
16540                                range: lsp::Range::new(
16541                                    lsp::Position::new(0, 12),
16542                                    lsp::Position::new(0, 15),
16543                                ),
16544                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16545                                ..Default::default()
16546                            },
16547                            lsp::Diagnostic {
16548                                range: lsp::Range::new(
16549                                    lsp::Position::new(0, 25),
16550                                    lsp::Position::new(0, 28),
16551                                ),
16552                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16553                                ..Default::default()
16554                            },
16555                        ],
16556                    },
16557                    None,
16558                    DiagnosticSourceKind::Pushed,
16559                    &[],
16560                    cx,
16561                )
16562                .unwrap()
16563        });
16564    });
16565
16566    executor.run_until_parked();
16567
16568    cx.update_editor(|editor, window, cx| {
16569        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16570    });
16571
16572    cx.assert_editor_state(indoc! {"
16573        fn func(abc def: i32) -> ˇu32 {
16574        }
16575    "});
16576
16577    cx.update_editor(|editor, window, cx| {
16578        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16579    });
16580
16581    cx.assert_editor_state(indoc! {"
16582        fn func(abc ˇdef: i32) -> u32 {
16583        }
16584    "});
16585
16586    cx.update_editor(|editor, window, cx| {
16587        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16588    });
16589
16590    cx.assert_editor_state(indoc! {"
16591        fn func(abcˇ def: i32) -> u32 {
16592        }
16593    "});
16594
16595    cx.update_editor(|editor, window, cx| {
16596        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16597    });
16598
16599    cx.assert_editor_state(indoc! {"
16600        fn func(abc def: i32) -> ˇu32 {
16601        }
16602    "});
16603}
16604
16605#[gpui::test]
16606async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16607    init_test(cx, |_| {});
16608
16609    let mut cx = EditorTestContext::new(cx).await;
16610
16611    let diff_base = r#"
16612        use some::mod;
16613
16614        const A: u32 = 42;
16615
16616        fn main() {
16617            println!("hello");
16618
16619            println!("world");
16620        }
16621        "#
16622    .unindent();
16623
16624    // Edits are modified, removed, modified, added
16625    cx.set_state(
16626        &r#"
16627        use some::modified;
16628
16629        ˇ
16630        fn main() {
16631            println!("hello there");
16632
16633            println!("around the");
16634            println!("world");
16635        }
16636        "#
16637        .unindent(),
16638    );
16639
16640    cx.set_head_text(&diff_base);
16641    executor.run_until_parked();
16642
16643    cx.update_editor(|editor, window, cx| {
16644        //Wrap around the bottom of the buffer
16645        for _ in 0..3 {
16646            editor.go_to_next_hunk(&GoToHunk, window, cx);
16647        }
16648    });
16649
16650    cx.assert_editor_state(
16651        &r#"
16652        ˇuse some::modified;
16653
16654
16655        fn main() {
16656            println!("hello there");
16657
16658            println!("around the");
16659            println!("world");
16660        }
16661        "#
16662        .unindent(),
16663    );
16664
16665    cx.update_editor(|editor, window, cx| {
16666        //Wrap around the top of the buffer
16667        for _ in 0..2 {
16668            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16669        }
16670    });
16671
16672    cx.assert_editor_state(
16673        &r#"
16674        use some::modified;
16675
16676
16677        fn main() {
16678        ˇ    println!("hello there");
16679
16680            println!("around the");
16681            println!("world");
16682        }
16683        "#
16684        .unindent(),
16685    );
16686
16687    cx.update_editor(|editor, window, cx| {
16688        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16689    });
16690
16691    cx.assert_editor_state(
16692        &r#"
16693        use some::modified;
16694
16695        ˇ
16696        fn main() {
16697            println!("hello there");
16698
16699            println!("around the");
16700            println!("world");
16701        }
16702        "#
16703        .unindent(),
16704    );
16705
16706    cx.update_editor(|editor, window, cx| {
16707        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16708    });
16709
16710    cx.assert_editor_state(
16711        &r#"
16712        ˇuse some::modified;
16713
16714
16715        fn main() {
16716            println!("hello there");
16717
16718            println!("around the");
16719            println!("world");
16720        }
16721        "#
16722        .unindent(),
16723    );
16724
16725    cx.update_editor(|editor, window, cx| {
16726        for _ in 0..2 {
16727            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16728        }
16729    });
16730
16731    cx.assert_editor_state(
16732        &r#"
16733        use some::modified;
16734
16735
16736        fn main() {
16737        ˇ    println!("hello there");
16738
16739            println!("around the");
16740            println!("world");
16741        }
16742        "#
16743        .unindent(),
16744    );
16745
16746    cx.update_editor(|editor, window, cx| {
16747        editor.fold(&Fold, window, cx);
16748    });
16749
16750    cx.update_editor(|editor, window, cx| {
16751        editor.go_to_next_hunk(&GoToHunk, window, cx);
16752    });
16753
16754    cx.assert_editor_state(
16755        &r#"
16756        ˇuse some::modified;
16757
16758
16759        fn main() {
16760            println!("hello there");
16761
16762            println!("around the");
16763            println!("world");
16764        }
16765        "#
16766        .unindent(),
16767    );
16768}
16769
16770#[test]
16771fn test_split_words() {
16772    fn split(text: &str) -> Vec<&str> {
16773        split_words(text).collect()
16774    }
16775
16776    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
16777    assert_eq!(split("hello_world"), &["hello_", "world"]);
16778    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
16779    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
16780    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
16781    assert_eq!(split("helloworld"), &["helloworld"]);
16782
16783    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
16784}
16785
16786#[gpui::test]
16787async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
16788    init_test(cx, |_| {});
16789
16790    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
16791    let mut assert = |before, after| {
16792        let _state_context = cx.set_state(before);
16793        cx.run_until_parked();
16794        cx.update_editor(|editor, window, cx| {
16795            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
16796        });
16797        cx.run_until_parked();
16798        cx.assert_editor_state(after);
16799    };
16800
16801    // Outside bracket jumps to outside of matching bracket
16802    assert("console.logˇ(var);", "console.log(var)ˇ;");
16803    assert("console.log(var)ˇ;", "console.logˇ(var);");
16804
16805    // Inside bracket jumps to inside of matching bracket
16806    assert("console.log(ˇvar);", "console.log(varˇ);");
16807    assert("console.log(varˇ);", "console.log(ˇvar);");
16808
16809    // When outside a bracket and inside, favor jumping to the inside bracket
16810    assert(
16811        "console.log('foo', [1, 2, 3]ˇ);",
16812        "console.log(ˇ'foo', [1, 2, 3]);",
16813    );
16814    assert(
16815        "console.log(ˇ'foo', [1, 2, 3]);",
16816        "console.log('foo', [1, 2, 3]ˇ);",
16817    );
16818
16819    // Bias forward if two options are equally likely
16820    assert(
16821        "let result = curried_fun()ˇ();",
16822        "let result = curried_fun()()ˇ;",
16823    );
16824
16825    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
16826    assert(
16827        indoc! {"
16828            function test() {
16829                console.log('test')ˇ
16830            }"},
16831        indoc! {"
16832            function test() {
16833                console.logˇ('test')
16834            }"},
16835    );
16836}
16837
16838#[gpui::test]
16839async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
16840    init_test(cx, |_| {});
16841
16842    let fs = FakeFs::new(cx.executor());
16843    fs.insert_tree(
16844        path!("/a"),
16845        json!({
16846            "main.rs": "fn main() { let a = 5; }",
16847            "other.rs": "// Test file",
16848        }),
16849    )
16850    .await;
16851    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16852
16853    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16854    language_registry.add(Arc::new(Language::new(
16855        LanguageConfig {
16856            name: "Rust".into(),
16857            matcher: LanguageMatcher {
16858                path_suffixes: vec!["rs".to_string()],
16859                ..Default::default()
16860            },
16861            brackets: BracketPairConfig {
16862                pairs: vec![BracketPair {
16863                    start: "{".to_string(),
16864                    end: "}".to_string(),
16865                    close: true,
16866                    surround: true,
16867                    newline: true,
16868                }],
16869                disabled_scopes_by_bracket_ix: Vec::new(),
16870            },
16871            ..Default::default()
16872        },
16873        Some(tree_sitter_rust::LANGUAGE.into()),
16874    )));
16875    let mut fake_servers = language_registry.register_fake_lsp(
16876        "Rust",
16877        FakeLspAdapter {
16878            capabilities: lsp::ServerCapabilities {
16879                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
16880                    first_trigger_character: "{".to_string(),
16881                    more_trigger_character: None,
16882                }),
16883                ..Default::default()
16884            },
16885            ..Default::default()
16886        },
16887    );
16888
16889    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16890
16891    let cx = &mut VisualTestContext::from_window(*workspace, cx);
16892
16893    let worktree_id = workspace
16894        .update(cx, |workspace, _, cx| {
16895            workspace.project().update(cx, |project, cx| {
16896                project.worktrees(cx).next().unwrap().read(cx).id()
16897            })
16898        })
16899        .unwrap();
16900
16901    let buffer = project
16902        .update(cx, |project, cx| {
16903            project.open_local_buffer(path!("/a/main.rs"), cx)
16904        })
16905        .await
16906        .unwrap();
16907    let editor_handle = workspace
16908        .update(cx, |workspace, window, cx| {
16909            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
16910        })
16911        .unwrap()
16912        .await
16913        .unwrap()
16914        .downcast::<Editor>()
16915        .unwrap();
16916
16917    cx.executor().start_waiting();
16918    let fake_server = fake_servers.next().await.unwrap();
16919
16920    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
16921        |params, _| async move {
16922            assert_eq!(
16923                params.text_document_position.text_document.uri,
16924                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
16925            );
16926            assert_eq!(
16927                params.text_document_position.position,
16928                lsp::Position::new(0, 21),
16929            );
16930
16931            Ok(Some(vec![lsp::TextEdit {
16932                new_text: "]".to_string(),
16933                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16934            }]))
16935        },
16936    );
16937
16938    editor_handle.update_in(cx, |editor, window, cx| {
16939        window.focus(&editor.focus_handle(cx));
16940        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16941            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
16942        });
16943        editor.handle_input("{", window, cx);
16944    });
16945
16946    cx.executor().run_until_parked();
16947
16948    buffer.update(cx, |buffer, _| {
16949        assert_eq!(
16950            buffer.text(),
16951            "fn main() { let a = {5}; }",
16952            "No extra braces from on type formatting should appear in the buffer"
16953        )
16954    });
16955}
16956
16957#[gpui::test(iterations = 20, seeds(31))]
16958async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
16959    init_test(cx, |_| {});
16960
16961    let mut cx = EditorLspTestContext::new_rust(
16962        lsp::ServerCapabilities {
16963            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
16964                first_trigger_character: ".".to_string(),
16965                more_trigger_character: None,
16966            }),
16967            ..Default::default()
16968        },
16969        cx,
16970    )
16971    .await;
16972
16973    cx.update_buffer(|buffer, _| {
16974        // This causes autoindent to be async.
16975        buffer.set_sync_parse_timeout(Duration::ZERO)
16976    });
16977
16978    cx.set_state("fn c() {\n    d()ˇ\n}\n");
16979    cx.simulate_keystroke("\n");
16980    cx.run_until_parked();
16981
16982    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
16983    let mut request =
16984        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
16985            let buffer_cloned = buffer_cloned.clone();
16986            async move {
16987                buffer_cloned.update(&mut cx, |buffer, _| {
16988                    assert_eq!(
16989                        buffer.text(),
16990                        "fn c() {\n    d()\n        .\n}\n",
16991                        "OnTypeFormatting should triggered after autoindent applied"
16992                    )
16993                })?;
16994
16995                Ok(Some(vec![]))
16996            }
16997        });
16998
16999    cx.simulate_keystroke(".");
17000    cx.run_until_parked();
17001
17002    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
17003    assert!(request.next().await.is_some());
17004    request.close();
17005    assert!(request.next().await.is_none());
17006}
17007
17008#[gpui::test]
17009async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17010    init_test(cx, |_| {});
17011
17012    let fs = FakeFs::new(cx.executor());
17013    fs.insert_tree(
17014        path!("/a"),
17015        json!({
17016            "main.rs": "fn main() { let a = 5; }",
17017            "other.rs": "// Test file",
17018        }),
17019    )
17020    .await;
17021
17022    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17023
17024    let server_restarts = Arc::new(AtomicUsize::new(0));
17025    let closure_restarts = Arc::clone(&server_restarts);
17026    let language_server_name = "test language server";
17027    let language_name: LanguageName = "Rust".into();
17028
17029    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17030    language_registry.add(Arc::new(Language::new(
17031        LanguageConfig {
17032            name: language_name.clone(),
17033            matcher: LanguageMatcher {
17034                path_suffixes: vec!["rs".to_string()],
17035                ..Default::default()
17036            },
17037            ..Default::default()
17038        },
17039        Some(tree_sitter_rust::LANGUAGE.into()),
17040    )));
17041    let mut fake_servers = language_registry.register_fake_lsp(
17042        "Rust",
17043        FakeLspAdapter {
17044            name: language_server_name,
17045            initialization_options: Some(json!({
17046                "testOptionValue": true
17047            })),
17048            initializer: Some(Box::new(move |fake_server| {
17049                let task_restarts = Arc::clone(&closure_restarts);
17050                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17051                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17052                    futures::future::ready(Ok(()))
17053                });
17054            })),
17055            ..Default::default()
17056        },
17057    );
17058
17059    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17060    let _buffer = project
17061        .update(cx, |project, cx| {
17062            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17063        })
17064        .await
17065        .unwrap();
17066    let _fake_server = fake_servers.next().await.unwrap();
17067    update_test_language_settings(cx, |language_settings| {
17068        language_settings.languages.0.insert(
17069            language_name.clone().0,
17070            LanguageSettingsContent {
17071                tab_size: NonZeroU32::new(8),
17072                ..Default::default()
17073            },
17074        );
17075    });
17076    cx.executor().run_until_parked();
17077    assert_eq!(
17078        server_restarts.load(atomic::Ordering::Acquire),
17079        0,
17080        "Should not restart LSP server on an unrelated change"
17081    );
17082
17083    update_test_project_settings(cx, |project_settings| {
17084        project_settings.lsp.insert(
17085            "Some other server name".into(),
17086            LspSettings {
17087                binary: None,
17088                settings: None,
17089                initialization_options: Some(json!({
17090                    "some other init value": false
17091                })),
17092                enable_lsp_tasks: false,
17093                fetch: None,
17094            },
17095        );
17096    });
17097    cx.executor().run_until_parked();
17098    assert_eq!(
17099        server_restarts.load(atomic::Ordering::Acquire),
17100        0,
17101        "Should not restart LSP server on an unrelated LSP settings change"
17102    );
17103
17104    update_test_project_settings(cx, |project_settings| {
17105        project_settings.lsp.insert(
17106            language_server_name.into(),
17107            LspSettings {
17108                binary: None,
17109                settings: None,
17110                initialization_options: Some(json!({
17111                    "anotherInitValue": false
17112                })),
17113                enable_lsp_tasks: false,
17114                fetch: None,
17115            },
17116        );
17117    });
17118    cx.executor().run_until_parked();
17119    assert_eq!(
17120        server_restarts.load(atomic::Ordering::Acquire),
17121        1,
17122        "Should restart LSP server on a related LSP settings change"
17123    );
17124
17125    update_test_project_settings(cx, |project_settings| {
17126        project_settings.lsp.insert(
17127            language_server_name.into(),
17128            LspSettings {
17129                binary: None,
17130                settings: None,
17131                initialization_options: Some(json!({
17132                    "anotherInitValue": false
17133                })),
17134                enable_lsp_tasks: false,
17135                fetch: None,
17136            },
17137        );
17138    });
17139    cx.executor().run_until_parked();
17140    assert_eq!(
17141        server_restarts.load(atomic::Ordering::Acquire),
17142        1,
17143        "Should not restart LSP server on a related LSP settings change that is the same"
17144    );
17145
17146    update_test_project_settings(cx, |project_settings| {
17147        project_settings.lsp.insert(
17148            language_server_name.into(),
17149            LspSettings {
17150                binary: None,
17151                settings: None,
17152                initialization_options: None,
17153                enable_lsp_tasks: false,
17154                fetch: None,
17155            },
17156        );
17157    });
17158    cx.executor().run_until_parked();
17159    assert_eq!(
17160        server_restarts.load(atomic::Ordering::Acquire),
17161        2,
17162        "Should restart LSP server on another related LSP settings change"
17163    );
17164}
17165
17166#[gpui::test]
17167async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17168    init_test(cx, |_| {});
17169
17170    let mut cx = EditorLspTestContext::new_rust(
17171        lsp::ServerCapabilities {
17172            completion_provider: Some(lsp::CompletionOptions {
17173                trigger_characters: Some(vec![".".to_string()]),
17174                resolve_provider: Some(true),
17175                ..Default::default()
17176            }),
17177            ..Default::default()
17178        },
17179        cx,
17180    )
17181    .await;
17182
17183    cx.set_state("fn main() { let a = 2ˇ; }");
17184    cx.simulate_keystroke(".");
17185    let completion_item = lsp::CompletionItem {
17186        label: "some".into(),
17187        kind: Some(lsp::CompletionItemKind::SNIPPET),
17188        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17189        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17190            kind: lsp::MarkupKind::Markdown,
17191            value: "```rust\nSome(2)\n```".to_string(),
17192        })),
17193        deprecated: Some(false),
17194        sort_text: Some("fffffff2".to_string()),
17195        filter_text: Some("some".to_string()),
17196        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17197        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17198            range: lsp::Range {
17199                start: lsp::Position {
17200                    line: 0,
17201                    character: 22,
17202                },
17203                end: lsp::Position {
17204                    line: 0,
17205                    character: 22,
17206                },
17207            },
17208            new_text: "Some(2)".to_string(),
17209        })),
17210        additional_text_edits: Some(vec![lsp::TextEdit {
17211            range: lsp::Range {
17212                start: lsp::Position {
17213                    line: 0,
17214                    character: 20,
17215                },
17216                end: lsp::Position {
17217                    line: 0,
17218                    character: 22,
17219                },
17220            },
17221            new_text: "".to_string(),
17222        }]),
17223        ..Default::default()
17224    };
17225
17226    let closure_completion_item = completion_item.clone();
17227    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17228        let task_completion_item = closure_completion_item.clone();
17229        async move {
17230            Ok(Some(lsp::CompletionResponse::Array(vec![
17231                task_completion_item,
17232            ])))
17233        }
17234    });
17235
17236    request.next().await;
17237
17238    cx.condition(|editor, _| editor.context_menu_visible())
17239        .await;
17240    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17241        editor
17242            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17243            .unwrap()
17244    });
17245    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17246
17247    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17248        let task_completion_item = completion_item.clone();
17249        async move { Ok(task_completion_item) }
17250    })
17251    .next()
17252    .await
17253    .unwrap();
17254    apply_additional_edits.await.unwrap();
17255    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17256}
17257
17258#[gpui::test]
17259async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17260    init_test(cx, |_| {});
17261
17262    let mut cx = EditorLspTestContext::new_rust(
17263        lsp::ServerCapabilities {
17264            completion_provider: Some(lsp::CompletionOptions {
17265                trigger_characters: Some(vec![".".to_string()]),
17266                resolve_provider: Some(true),
17267                ..Default::default()
17268            }),
17269            ..Default::default()
17270        },
17271        cx,
17272    )
17273    .await;
17274
17275    cx.set_state("fn main() { let a = 2ˇ; }");
17276    cx.simulate_keystroke(".");
17277
17278    let item1 = lsp::CompletionItem {
17279        label: "method id()".to_string(),
17280        filter_text: Some("id".to_string()),
17281        detail: None,
17282        documentation: None,
17283        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17284            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17285            new_text: ".id".to_string(),
17286        })),
17287        ..lsp::CompletionItem::default()
17288    };
17289
17290    let item2 = lsp::CompletionItem {
17291        label: "other".to_string(),
17292        filter_text: Some("other".to_string()),
17293        detail: None,
17294        documentation: None,
17295        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17296            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17297            new_text: ".other".to_string(),
17298        })),
17299        ..lsp::CompletionItem::default()
17300    };
17301
17302    let item1 = item1.clone();
17303    cx.set_request_handler::<lsp::request::Completion, _, _>({
17304        let item1 = item1.clone();
17305        move |_, _, _| {
17306            let item1 = item1.clone();
17307            let item2 = item2.clone();
17308            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17309        }
17310    })
17311    .next()
17312    .await;
17313
17314    cx.condition(|editor, _| editor.context_menu_visible())
17315        .await;
17316    cx.update_editor(|editor, _, _| {
17317        let context_menu = editor.context_menu.borrow_mut();
17318        let context_menu = context_menu
17319            .as_ref()
17320            .expect("Should have the context menu deployed");
17321        match context_menu {
17322            CodeContextMenu::Completions(completions_menu) => {
17323                let completions = completions_menu.completions.borrow_mut();
17324                assert_eq!(
17325                    completions
17326                        .iter()
17327                        .map(|completion| &completion.label.text)
17328                        .collect::<Vec<_>>(),
17329                    vec!["method id()", "other"]
17330                )
17331            }
17332            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17333        }
17334    });
17335
17336    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17337        let item1 = item1.clone();
17338        move |_, item_to_resolve, _| {
17339            let item1 = item1.clone();
17340            async move {
17341                if item1 == item_to_resolve {
17342                    Ok(lsp::CompletionItem {
17343                        label: "method id()".to_string(),
17344                        filter_text: Some("id".to_string()),
17345                        detail: Some("Now resolved!".to_string()),
17346                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17347                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17348                            range: lsp::Range::new(
17349                                lsp::Position::new(0, 22),
17350                                lsp::Position::new(0, 22),
17351                            ),
17352                            new_text: ".id".to_string(),
17353                        })),
17354                        ..lsp::CompletionItem::default()
17355                    })
17356                } else {
17357                    Ok(item_to_resolve)
17358                }
17359            }
17360        }
17361    })
17362    .next()
17363    .await
17364    .unwrap();
17365    cx.run_until_parked();
17366
17367    cx.update_editor(|editor, window, cx| {
17368        editor.context_menu_next(&Default::default(), window, cx);
17369    });
17370
17371    cx.update_editor(|editor, _, _| {
17372        let context_menu = editor.context_menu.borrow_mut();
17373        let context_menu = context_menu
17374            .as_ref()
17375            .expect("Should have the context menu deployed");
17376        match context_menu {
17377            CodeContextMenu::Completions(completions_menu) => {
17378                let completions = completions_menu.completions.borrow_mut();
17379                assert_eq!(
17380                    completions
17381                        .iter()
17382                        .map(|completion| &completion.label.text)
17383                        .collect::<Vec<_>>(),
17384                    vec!["method id() Now resolved!", "other"],
17385                    "Should update first completion label, but not second as the filter text did not match."
17386                );
17387            }
17388            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17389        }
17390    });
17391}
17392
17393#[gpui::test]
17394async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17395    init_test(cx, |_| {});
17396    let mut cx = EditorLspTestContext::new_rust(
17397        lsp::ServerCapabilities {
17398            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17399            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17400            completion_provider: Some(lsp::CompletionOptions {
17401                resolve_provider: Some(true),
17402                ..Default::default()
17403            }),
17404            ..Default::default()
17405        },
17406        cx,
17407    )
17408    .await;
17409    cx.set_state(indoc! {"
17410        struct TestStruct {
17411            field: i32
17412        }
17413
17414        fn mainˇ() {
17415            let unused_var = 42;
17416            let test_struct = TestStruct { field: 42 };
17417        }
17418    "});
17419    let symbol_range = cx.lsp_range(indoc! {"
17420        struct TestStruct {
17421            field: i32
17422        }
17423
17424        «fn main»() {
17425            let unused_var = 42;
17426            let test_struct = TestStruct { field: 42 };
17427        }
17428    "});
17429    let mut hover_requests =
17430        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17431            Ok(Some(lsp::Hover {
17432                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17433                    kind: lsp::MarkupKind::Markdown,
17434                    value: "Function documentation".to_string(),
17435                }),
17436                range: Some(symbol_range),
17437            }))
17438        });
17439
17440    // Case 1: Test that code action menu hide hover popover
17441    cx.dispatch_action(Hover);
17442    hover_requests.next().await;
17443    cx.condition(|editor, _| editor.hover_state.visible()).await;
17444    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17445        move |_, _, _| async move {
17446            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17447                lsp::CodeAction {
17448                    title: "Remove unused variable".to_string(),
17449                    kind: Some(CodeActionKind::QUICKFIX),
17450                    edit: Some(lsp::WorkspaceEdit {
17451                        changes: Some(
17452                            [(
17453                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17454                                vec![lsp::TextEdit {
17455                                    range: lsp::Range::new(
17456                                        lsp::Position::new(5, 4),
17457                                        lsp::Position::new(5, 27),
17458                                    ),
17459                                    new_text: "".to_string(),
17460                                }],
17461                            )]
17462                            .into_iter()
17463                            .collect(),
17464                        ),
17465                        ..Default::default()
17466                    }),
17467                    ..Default::default()
17468                },
17469            )]))
17470        },
17471    );
17472    cx.update_editor(|editor, window, cx| {
17473        editor.toggle_code_actions(
17474            &ToggleCodeActions {
17475                deployed_from: None,
17476                quick_launch: false,
17477            },
17478            window,
17479            cx,
17480        );
17481    });
17482    code_action_requests.next().await;
17483    cx.run_until_parked();
17484    cx.condition(|editor, _| editor.context_menu_visible())
17485        .await;
17486    cx.update_editor(|editor, _, _| {
17487        assert!(
17488            !editor.hover_state.visible(),
17489            "Hover popover should be hidden when code action menu is shown"
17490        );
17491        // Hide code actions
17492        editor.context_menu.take();
17493    });
17494
17495    // Case 2: Test that code completions hide hover popover
17496    cx.dispatch_action(Hover);
17497    hover_requests.next().await;
17498    cx.condition(|editor, _| editor.hover_state.visible()).await;
17499    let counter = Arc::new(AtomicUsize::new(0));
17500    let mut completion_requests =
17501        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17502            let counter = counter.clone();
17503            async move {
17504                counter.fetch_add(1, atomic::Ordering::Release);
17505                Ok(Some(lsp::CompletionResponse::Array(vec![
17506                    lsp::CompletionItem {
17507                        label: "main".into(),
17508                        kind: Some(lsp::CompletionItemKind::FUNCTION),
17509                        detail: Some("() -> ()".to_string()),
17510                        ..Default::default()
17511                    },
17512                    lsp::CompletionItem {
17513                        label: "TestStruct".into(),
17514                        kind: Some(lsp::CompletionItemKind::STRUCT),
17515                        detail: Some("struct TestStruct".to_string()),
17516                        ..Default::default()
17517                    },
17518                ])))
17519            }
17520        });
17521    cx.update_editor(|editor, window, cx| {
17522        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
17523    });
17524    completion_requests.next().await;
17525    cx.condition(|editor, _| editor.context_menu_visible())
17526        .await;
17527    cx.update_editor(|editor, _, _| {
17528        assert!(
17529            !editor.hover_state.visible(),
17530            "Hover popover should be hidden when completion menu is shown"
17531        );
17532    });
17533}
17534
17535#[gpui::test]
17536async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
17537    init_test(cx, |_| {});
17538
17539    let mut cx = EditorLspTestContext::new_rust(
17540        lsp::ServerCapabilities {
17541            completion_provider: Some(lsp::CompletionOptions {
17542                trigger_characters: Some(vec![".".to_string()]),
17543                resolve_provider: Some(true),
17544                ..Default::default()
17545            }),
17546            ..Default::default()
17547        },
17548        cx,
17549    )
17550    .await;
17551
17552    cx.set_state("fn main() { let a = 2ˇ; }");
17553    cx.simulate_keystroke(".");
17554
17555    let unresolved_item_1 = lsp::CompletionItem {
17556        label: "id".to_string(),
17557        filter_text: Some("id".to_string()),
17558        detail: None,
17559        documentation: None,
17560        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17561            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17562            new_text: ".id".to_string(),
17563        })),
17564        ..lsp::CompletionItem::default()
17565    };
17566    let resolved_item_1 = lsp::CompletionItem {
17567        additional_text_edits: Some(vec![lsp::TextEdit {
17568            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17569            new_text: "!!".to_string(),
17570        }]),
17571        ..unresolved_item_1.clone()
17572    };
17573    let unresolved_item_2 = lsp::CompletionItem {
17574        label: "other".to_string(),
17575        filter_text: Some("other".to_string()),
17576        detail: None,
17577        documentation: None,
17578        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17579            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17580            new_text: ".other".to_string(),
17581        })),
17582        ..lsp::CompletionItem::default()
17583    };
17584    let resolved_item_2 = lsp::CompletionItem {
17585        additional_text_edits: Some(vec![lsp::TextEdit {
17586            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17587            new_text: "??".to_string(),
17588        }]),
17589        ..unresolved_item_2.clone()
17590    };
17591
17592    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
17593    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
17594    cx.lsp
17595        .server
17596        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17597            let unresolved_item_1 = unresolved_item_1.clone();
17598            let resolved_item_1 = resolved_item_1.clone();
17599            let unresolved_item_2 = unresolved_item_2.clone();
17600            let resolved_item_2 = resolved_item_2.clone();
17601            let resolve_requests_1 = resolve_requests_1.clone();
17602            let resolve_requests_2 = resolve_requests_2.clone();
17603            move |unresolved_request, _| {
17604                let unresolved_item_1 = unresolved_item_1.clone();
17605                let resolved_item_1 = resolved_item_1.clone();
17606                let unresolved_item_2 = unresolved_item_2.clone();
17607                let resolved_item_2 = resolved_item_2.clone();
17608                let resolve_requests_1 = resolve_requests_1.clone();
17609                let resolve_requests_2 = resolve_requests_2.clone();
17610                async move {
17611                    if unresolved_request == unresolved_item_1 {
17612                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
17613                        Ok(resolved_item_1.clone())
17614                    } else if unresolved_request == unresolved_item_2 {
17615                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
17616                        Ok(resolved_item_2.clone())
17617                    } else {
17618                        panic!("Unexpected completion item {unresolved_request:?}")
17619                    }
17620                }
17621            }
17622        })
17623        .detach();
17624
17625    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17626        let unresolved_item_1 = unresolved_item_1.clone();
17627        let unresolved_item_2 = unresolved_item_2.clone();
17628        async move {
17629            Ok(Some(lsp::CompletionResponse::Array(vec![
17630                unresolved_item_1,
17631                unresolved_item_2,
17632            ])))
17633        }
17634    })
17635    .next()
17636    .await;
17637
17638    cx.condition(|editor, _| editor.context_menu_visible())
17639        .await;
17640    cx.update_editor(|editor, _, _| {
17641        let context_menu = editor.context_menu.borrow_mut();
17642        let context_menu = context_menu
17643            .as_ref()
17644            .expect("Should have the context menu deployed");
17645        match context_menu {
17646            CodeContextMenu::Completions(completions_menu) => {
17647                let completions = completions_menu.completions.borrow_mut();
17648                assert_eq!(
17649                    completions
17650                        .iter()
17651                        .map(|completion| &completion.label.text)
17652                        .collect::<Vec<_>>(),
17653                    vec!["id", "other"]
17654                )
17655            }
17656            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17657        }
17658    });
17659    cx.run_until_parked();
17660
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.context_menu_prev(&ContextMenuPrevious, window, cx);
17667    });
17668    cx.run_until_parked();
17669    cx.update_editor(|editor, window, cx| {
17670        editor.context_menu_next(&ContextMenuNext, window, cx);
17671    });
17672    cx.run_until_parked();
17673    cx.update_editor(|editor, window, cx| {
17674        editor
17675            .compose_completion(&ComposeCompletion::default(), window, cx)
17676            .expect("No task returned")
17677    })
17678    .await
17679    .expect("Completion failed");
17680    cx.run_until_parked();
17681
17682    cx.update_editor(|editor, _, cx| {
17683        assert_eq!(
17684            resolve_requests_1.load(atomic::Ordering::Acquire),
17685            1,
17686            "Should always resolve once despite multiple selections"
17687        );
17688        assert_eq!(
17689            resolve_requests_2.load(atomic::Ordering::Acquire),
17690            1,
17691            "Should always resolve once after multiple selections and applying the completion"
17692        );
17693        assert_eq!(
17694            editor.text(cx),
17695            "fn main() { let a = ??.other; }",
17696            "Should use resolved data when applying the completion"
17697        );
17698    });
17699}
17700
17701#[gpui::test]
17702async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
17703    init_test(cx, |_| {});
17704
17705    let item_0 = lsp::CompletionItem {
17706        label: "abs".into(),
17707        insert_text: Some("abs".into()),
17708        data: Some(json!({ "very": "special"})),
17709        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
17710        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
17711            lsp::InsertReplaceEdit {
17712                new_text: "abs".to_string(),
17713                insert: lsp::Range::default(),
17714                replace: lsp::Range::default(),
17715            },
17716        )),
17717        ..lsp::CompletionItem::default()
17718    };
17719    let items = iter::once(item_0.clone())
17720        .chain((11..51).map(|i| lsp::CompletionItem {
17721            label: format!("item_{}", i),
17722            insert_text: Some(format!("item_{}", i)),
17723            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
17724            ..lsp::CompletionItem::default()
17725        }))
17726        .collect::<Vec<_>>();
17727
17728    let default_commit_characters = vec!["?".to_string()];
17729    let default_data = json!({ "default": "data"});
17730    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
17731    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
17732    let default_edit_range = lsp::Range {
17733        start: lsp::Position {
17734            line: 0,
17735            character: 5,
17736        },
17737        end: lsp::Position {
17738            line: 0,
17739            character: 5,
17740        },
17741    };
17742
17743    let mut cx = EditorLspTestContext::new_rust(
17744        lsp::ServerCapabilities {
17745            completion_provider: Some(lsp::CompletionOptions {
17746                trigger_characters: Some(vec![".".to_string()]),
17747                resolve_provider: Some(true),
17748                ..Default::default()
17749            }),
17750            ..Default::default()
17751        },
17752        cx,
17753    )
17754    .await;
17755
17756    cx.set_state("fn main() { let a = 2ˇ; }");
17757    cx.simulate_keystroke(".");
17758
17759    let completion_data = default_data.clone();
17760    let completion_characters = default_commit_characters.clone();
17761    let completion_items = items.clone();
17762    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17763        let default_data = completion_data.clone();
17764        let default_commit_characters = completion_characters.clone();
17765        let items = completion_items.clone();
17766        async move {
17767            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
17768                items,
17769                item_defaults: Some(lsp::CompletionListItemDefaults {
17770                    data: Some(default_data.clone()),
17771                    commit_characters: Some(default_commit_characters.clone()),
17772                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
17773                        default_edit_range,
17774                    )),
17775                    insert_text_format: Some(default_insert_text_format),
17776                    insert_text_mode: Some(default_insert_text_mode),
17777                }),
17778                ..lsp::CompletionList::default()
17779            })))
17780        }
17781    })
17782    .next()
17783    .await;
17784
17785    let resolved_items = Arc::new(Mutex::new(Vec::new()));
17786    cx.lsp
17787        .server
17788        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17789            let closure_resolved_items = resolved_items.clone();
17790            move |item_to_resolve, _| {
17791                let closure_resolved_items = closure_resolved_items.clone();
17792                async move {
17793                    closure_resolved_items.lock().push(item_to_resolve.clone());
17794                    Ok(item_to_resolve)
17795                }
17796            }
17797        })
17798        .detach();
17799
17800    cx.condition(|editor, _| editor.context_menu_visible())
17801        .await;
17802    cx.run_until_parked();
17803    cx.update_editor(|editor, _, _| {
17804        let menu = editor.context_menu.borrow_mut();
17805        match menu.as_ref().expect("should have the completions menu") {
17806            CodeContextMenu::Completions(completions_menu) => {
17807                assert_eq!(
17808                    completions_menu
17809                        .entries
17810                        .borrow()
17811                        .iter()
17812                        .map(|mat| mat.string.clone())
17813                        .collect::<Vec<String>>(),
17814                    items
17815                        .iter()
17816                        .map(|completion| completion.label.clone())
17817                        .collect::<Vec<String>>()
17818                );
17819            }
17820            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
17821        }
17822    });
17823    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
17824    // with 4 from the end.
17825    assert_eq!(
17826        *resolved_items.lock(),
17827        [&items[0..16], &items[items.len() - 4..items.len()]]
17828            .concat()
17829            .iter()
17830            .cloned()
17831            .map(|mut item| {
17832                if item.data.is_none() {
17833                    item.data = Some(default_data.clone());
17834                }
17835                item
17836            })
17837            .collect::<Vec<lsp::CompletionItem>>(),
17838        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
17839    );
17840    resolved_items.lock().clear();
17841
17842    cx.update_editor(|editor, window, cx| {
17843        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17844    });
17845    cx.run_until_parked();
17846    // Completions that have already been resolved are skipped.
17847    assert_eq!(
17848        *resolved_items.lock(),
17849        items[items.len() - 17..items.len() - 4]
17850            .iter()
17851            .cloned()
17852            .map(|mut item| {
17853                if item.data.is_none() {
17854                    item.data = Some(default_data.clone());
17855                }
17856                item
17857            })
17858            .collect::<Vec<lsp::CompletionItem>>()
17859    );
17860    resolved_items.lock().clear();
17861}
17862
17863#[gpui::test]
17864async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
17865    init_test(cx, |_| {});
17866
17867    let mut cx = EditorLspTestContext::new(
17868        Language::new(
17869            LanguageConfig {
17870                matcher: LanguageMatcher {
17871                    path_suffixes: vec!["jsx".into()],
17872                    ..Default::default()
17873                },
17874                overrides: [(
17875                    "element".into(),
17876                    LanguageConfigOverride {
17877                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
17878                        ..Default::default()
17879                    },
17880                )]
17881                .into_iter()
17882                .collect(),
17883                ..Default::default()
17884            },
17885            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
17886        )
17887        .with_override_query("(jsx_self_closing_element) @element")
17888        .unwrap(),
17889        lsp::ServerCapabilities {
17890            completion_provider: Some(lsp::CompletionOptions {
17891                trigger_characters: Some(vec![":".to_string()]),
17892                ..Default::default()
17893            }),
17894            ..Default::default()
17895        },
17896        cx,
17897    )
17898    .await;
17899
17900    cx.lsp
17901        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
17902            Ok(Some(lsp::CompletionResponse::Array(vec![
17903                lsp::CompletionItem {
17904                    label: "bg-blue".into(),
17905                    ..Default::default()
17906                },
17907                lsp::CompletionItem {
17908                    label: "bg-red".into(),
17909                    ..Default::default()
17910                },
17911                lsp::CompletionItem {
17912                    label: "bg-yellow".into(),
17913                    ..Default::default()
17914                },
17915            ])))
17916        });
17917
17918    cx.set_state(r#"<p class="bgˇ" />"#);
17919
17920    // Trigger completion when typing a dash, because the dash is an extra
17921    // word character in the 'element' scope, which contains the cursor.
17922    cx.simulate_keystroke("-");
17923    cx.executor().run_until_parked();
17924    cx.update_editor(|editor, _, _| {
17925        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17926        {
17927            assert_eq!(
17928                completion_menu_entries(menu),
17929                &["bg-blue", "bg-red", "bg-yellow"]
17930            );
17931        } else {
17932            panic!("expected completion menu to be open");
17933        }
17934    });
17935
17936    cx.simulate_keystroke("l");
17937    cx.executor().run_until_parked();
17938    cx.update_editor(|editor, _, _| {
17939        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17940        {
17941            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
17942        } else {
17943            panic!("expected completion menu to be open");
17944        }
17945    });
17946
17947    // When filtering completions, consider the character after the '-' to
17948    // be the start of a subword.
17949    cx.set_state(r#"<p class="yelˇ" />"#);
17950    cx.simulate_keystroke("l");
17951    cx.executor().run_until_parked();
17952    cx.update_editor(|editor, _, _| {
17953        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17954        {
17955            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
17956        } else {
17957            panic!("expected completion menu to be open");
17958        }
17959    });
17960}
17961
17962fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
17963    let entries = menu.entries.borrow();
17964    entries.iter().map(|mat| mat.string.clone()).collect()
17965}
17966
17967#[gpui::test]
17968async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
17969    init_test(cx, |settings| {
17970        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
17971            Formatter::Prettier,
17972        )))
17973    });
17974
17975    let fs = FakeFs::new(cx.executor());
17976    fs.insert_file(path!("/file.ts"), Default::default()).await;
17977
17978    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
17979    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17980
17981    language_registry.add(Arc::new(Language::new(
17982        LanguageConfig {
17983            name: "TypeScript".into(),
17984            matcher: LanguageMatcher {
17985                path_suffixes: vec!["ts".to_string()],
17986                ..Default::default()
17987            },
17988            ..Default::default()
17989        },
17990        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
17991    )));
17992    update_test_language_settings(cx, |settings| {
17993        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
17994    });
17995
17996    let test_plugin = "test_plugin";
17997    let _ = language_registry.register_fake_lsp(
17998        "TypeScript",
17999        FakeLspAdapter {
18000            prettier_plugins: vec![test_plugin],
18001            ..Default::default()
18002        },
18003    );
18004
18005    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
18006    let buffer = project
18007        .update(cx, |project, cx| {
18008            project.open_local_buffer(path!("/file.ts"), cx)
18009        })
18010        .await
18011        .unwrap();
18012
18013    let buffer_text = "one\ntwo\nthree\n";
18014    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18015    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18016    editor.update_in(cx, |editor, window, cx| {
18017        editor.set_text(buffer_text, window, cx)
18018    });
18019
18020    editor
18021        .update_in(cx, |editor, window, cx| {
18022            editor.perform_format(
18023                project.clone(),
18024                FormatTrigger::Manual,
18025                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18026                window,
18027                cx,
18028            )
18029        })
18030        .unwrap()
18031        .await;
18032    assert_eq!(
18033        editor.update(cx, |editor, cx| editor.text(cx)),
18034        buffer_text.to_string() + prettier_format_suffix,
18035        "Test prettier formatting was not applied to the original buffer text",
18036    );
18037
18038    update_test_language_settings(cx, |settings| {
18039        settings.defaults.formatter = Some(SelectedFormatter::Auto)
18040    });
18041    let format = editor.update_in(cx, |editor, window, cx| {
18042        editor.perform_format(
18043            project.clone(),
18044            FormatTrigger::Manual,
18045            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18046            window,
18047            cx,
18048        )
18049    });
18050    format.await.unwrap();
18051    assert_eq!(
18052        editor.update(cx, |editor, cx| editor.text(cx)),
18053        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18054        "Autoformatting (via test prettier) was not applied to the original buffer text",
18055    );
18056}
18057
18058#[gpui::test]
18059async fn test_addition_reverts(cx: &mut TestAppContext) {
18060    init_test(cx, |_| {});
18061    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18062    let base_text = indoc! {r#"
18063        struct Row;
18064        struct Row1;
18065        struct Row2;
18066
18067        struct Row4;
18068        struct Row5;
18069        struct Row6;
18070
18071        struct Row8;
18072        struct Row9;
18073        struct Row10;"#};
18074
18075    // When addition hunks are not adjacent to carets, no hunk revert is performed
18076    assert_hunk_revert(
18077        indoc! {r#"struct Row;
18078                   struct Row1;
18079                   struct Row1.1;
18080                   struct Row1.2;
18081                   struct Row2;ˇ
18082
18083                   struct Row4;
18084                   struct Row5;
18085                   struct Row6;
18086
18087                   struct Row8;
18088                   ˇstruct Row9;
18089                   struct Row9.1;
18090                   struct Row9.2;
18091                   struct Row9.3;
18092                   struct Row10;"#},
18093        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18094        indoc! {r#"struct Row;
18095                   struct Row1;
18096                   struct Row1.1;
18097                   struct Row1.2;
18098                   struct Row2;ˇ
18099
18100                   struct Row4;
18101                   struct Row5;
18102                   struct Row6;
18103
18104                   struct Row8;
18105                   ˇstruct Row9;
18106                   struct Row9.1;
18107                   struct Row9.2;
18108                   struct Row9.3;
18109                   struct Row10;"#},
18110        base_text,
18111        &mut cx,
18112    );
18113    // Same for selections
18114    assert_hunk_revert(
18115        indoc! {r#"struct Row;
18116                   struct Row1;
18117                   struct Row2;
18118                   struct Row2.1;
18119                   struct Row2.2;
18120                   «ˇ
18121                   struct Row4;
18122                   struct» Row5;
18123                   «struct Row6;
18124                   ˇ»
18125                   struct Row9.1;
18126                   struct Row9.2;
18127                   struct Row9.3;
18128                   struct Row8;
18129                   struct Row9;
18130                   struct Row10;"#},
18131        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18132        indoc! {r#"struct Row;
18133                   struct Row1;
18134                   struct Row2;
18135                   struct Row2.1;
18136                   struct Row2.2;
18137                   «ˇ
18138                   struct Row4;
18139                   struct» Row5;
18140                   «struct Row6;
18141                   ˇ»
18142                   struct Row9.1;
18143                   struct Row9.2;
18144                   struct Row9.3;
18145                   struct Row8;
18146                   struct Row9;
18147                   struct Row10;"#},
18148        base_text,
18149        &mut cx,
18150    );
18151
18152    // When carets and selections intersect the addition hunks, those are reverted.
18153    // Adjacent carets got merged.
18154    assert_hunk_revert(
18155        indoc! {r#"struct Row;
18156                   ˇ// something on the top
18157                   struct Row1;
18158                   struct Row2;
18159                   struct Roˇw3.1;
18160                   struct Row2.2;
18161                   struct Row2.3;ˇ
18162
18163                   struct Row4;
18164                   struct ˇRow5.1;
18165                   struct Row5.2;
18166                   struct «Rowˇ»5.3;
18167                   struct Row5;
18168                   struct Row6;
18169                   ˇ
18170                   struct Row9.1;
18171                   struct «Rowˇ»9.2;
18172                   struct «ˇRow»9.3;
18173                   struct Row8;
18174                   struct Row9;
18175                   «ˇ// something on bottom»
18176                   struct Row10;"#},
18177        vec![
18178            DiffHunkStatusKind::Added,
18179            DiffHunkStatusKind::Added,
18180            DiffHunkStatusKind::Added,
18181            DiffHunkStatusKind::Added,
18182            DiffHunkStatusKind::Added,
18183        ],
18184        indoc! {r#"struct Row;
18185                   ˇstruct Row1;
18186                   struct Row2;
18187                   ˇ
18188                   struct Row4;
18189                   ˇstruct Row5;
18190                   struct Row6;
18191                   ˇ
18192                   ˇstruct Row8;
18193                   struct Row9;
18194                   ˇstruct Row10;"#},
18195        base_text,
18196        &mut cx,
18197    );
18198}
18199
18200#[gpui::test]
18201async fn test_modification_reverts(cx: &mut TestAppContext) {
18202    init_test(cx, |_| {});
18203    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18204    let base_text = indoc! {r#"
18205        struct Row;
18206        struct Row1;
18207        struct Row2;
18208
18209        struct Row4;
18210        struct Row5;
18211        struct Row6;
18212
18213        struct Row8;
18214        struct Row9;
18215        struct Row10;"#};
18216
18217    // Modification hunks behave the same as the addition ones.
18218    assert_hunk_revert(
18219        indoc! {r#"struct Row;
18220                   struct Row1;
18221                   struct Row33;
18222                   ˇ
18223                   struct Row4;
18224                   struct Row5;
18225                   struct Row6;
18226                   ˇ
18227                   struct Row99;
18228                   struct Row9;
18229                   struct Row10;"#},
18230        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18231        indoc! {r#"struct Row;
18232                   struct Row1;
18233                   struct Row33;
18234                   ˇ
18235                   struct Row4;
18236                   struct Row5;
18237                   struct Row6;
18238                   ˇ
18239                   struct Row99;
18240                   struct Row9;
18241                   struct Row10;"#},
18242        base_text,
18243        &mut cx,
18244    );
18245    assert_hunk_revert(
18246        indoc! {r#"struct Row;
18247                   struct Row1;
18248                   struct Row33;
18249                   «ˇ
18250                   struct Row4;
18251                   struct» Row5;
18252                   «struct Row6;
18253                   ˇ»
18254                   struct Row99;
18255                   struct Row9;
18256                   struct Row10;"#},
18257        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18258        indoc! {r#"struct Row;
18259                   struct Row1;
18260                   struct Row33;
18261                   «ˇ
18262                   struct Row4;
18263                   struct» Row5;
18264                   «struct Row6;
18265                   ˇ»
18266                   struct Row99;
18267                   struct Row9;
18268                   struct Row10;"#},
18269        base_text,
18270        &mut cx,
18271    );
18272
18273    assert_hunk_revert(
18274        indoc! {r#"ˇstruct Row1.1;
18275                   struct Row1;
18276                   «ˇstr»uct Row22;
18277
18278                   struct ˇRow44;
18279                   struct Row5;
18280                   struct «Rˇ»ow66;ˇ
18281
18282                   «struˇ»ct Row88;
18283                   struct Row9;
18284                   struct Row1011;ˇ"#},
18285        vec![
18286            DiffHunkStatusKind::Modified,
18287            DiffHunkStatusKind::Modified,
18288            DiffHunkStatusKind::Modified,
18289            DiffHunkStatusKind::Modified,
18290            DiffHunkStatusKind::Modified,
18291            DiffHunkStatusKind::Modified,
18292        ],
18293        indoc! {r#"struct Row;
18294                   ˇstruct Row1;
18295                   struct Row2;
18296                   ˇ
18297                   struct Row4;
18298                   ˇstruct Row5;
18299                   struct Row6;
18300                   ˇ
18301                   struct Row8;
18302                   ˇstruct Row9;
18303                   struct Row10;ˇ"#},
18304        base_text,
18305        &mut cx,
18306    );
18307}
18308
18309#[gpui::test]
18310async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18311    init_test(cx, |_| {});
18312    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18313    let base_text = indoc! {r#"
18314        one
18315
18316        two
18317        three
18318        "#};
18319
18320    cx.set_head_text(base_text);
18321    cx.set_state("\nˇ\n");
18322    cx.executor().run_until_parked();
18323    cx.update_editor(|editor, _window, cx| {
18324        editor.expand_selected_diff_hunks(cx);
18325    });
18326    cx.executor().run_until_parked();
18327    cx.update_editor(|editor, window, cx| {
18328        editor.backspace(&Default::default(), window, cx);
18329    });
18330    cx.run_until_parked();
18331    cx.assert_state_with_diff(
18332        indoc! {r#"
18333
18334        - two
18335        - threeˇ
18336        +
18337        "#}
18338        .to_string(),
18339    );
18340}
18341
18342#[gpui::test]
18343async fn test_deletion_reverts(cx: &mut TestAppContext) {
18344    init_test(cx, |_| {});
18345    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18346    let base_text = indoc! {r#"struct Row;
18347struct Row1;
18348struct Row2;
18349
18350struct Row4;
18351struct Row5;
18352struct Row6;
18353
18354struct Row8;
18355struct Row9;
18356struct Row10;"#};
18357
18358    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18359    assert_hunk_revert(
18360        indoc! {r#"struct Row;
18361                   struct Row2;
18362
18363                   ˇstruct Row4;
18364                   struct Row5;
18365                   struct Row6;
18366                   ˇ
18367                   struct Row8;
18368                   struct Row10;"#},
18369        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18370        indoc! {r#"struct Row;
18371                   struct Row2;
18372
18373                   ˇstruct Row4;
18374                   struct Row5;
18375                   struct Row6;
18376                   ˇ
18377                   struct Row8;
18378                   struct Row10;"#},
18379        base_text,
18380        &mut cx,
18381    );
18382    assert_hunk_revert(
18383        indoc! {r#"struct Row;
18384                   struct Row2;
18385
18386                   «ˇstruct Row4;
18387                   struct» Row5;
18388                   «struct Row6;
18389                   ˇ»
18390                   struct Row8;
18391                   struct Row10;"#},
18392        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18393        indoc! {r#"struct Row;
18394                   struct Row2;
18395
18396                   «ˇstruct Row4;
18397                   struct» Row5;
18398                   «struct Row6;
18399                   ˇ»
18400                   struct Row8;
18401                   struct Row10;"#},
18402        base_text,
18403        &mut cx,
18404    );
18405
18406    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18407    assert_hunk_revert(
18408        indoc! {r#"struct Row;
18409                   ˇstruct Row2;
18410
18411                   struct Row4;
18412                   struct Row5;
18413                   struct Row6;
18414
18415                   struct Row8;ˇ
18416                   struct Row10;"#},
18417        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18418        indoc! {r#"struct Row;
18419                   struct Row1;
18420                   ˇstruct Row2;
18421
18422                   struct Row4;
18423                   struct Row5;
18424                   struct Row6;
18425
18426                   struct Row8;ˇ
18427                   struct Row9;
18428                   struct Row10;"#},
18429        base_text,
18430        &mut cx,
18431    );
18432    assert_hunk_revert(
18433        indoc! {r#"struct Row;
18434                   struct Row2«ˇ;
18435                   struct Row4;
18436                   struct» Row5;
18437                   «struct Row6;
18438
18439                   struct Row8;ˇ»
18440                   struct Row10;"#},
18441        vec![
18442            DiffHunkStatusKind::Deleted,
18443            DiffHunkStatusKind::Deleted,
18444            DiffHunkStatusKind::Deleted,
18445        ],
18446        indoc! {r#"struct Row;
18447                   struct Row1;
18448                   struct Row2«ˇ;
18449
18450                   struct Row4;
18451                   struct» Row5;
18452                   «struct Row6;
18453
18454                   struct Row8;ˇ»
18455                   struct Row9;
18456                   struct Row10;"#},
18457        base_text,
18458        &mut cx,
18459    );
18460}
18461
18462#[gpui::test]
18463async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18464    init_test(cx, |_| {});
18465
18466    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18467    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18468    let base_text_3 =
18469        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18470
18471    let text_1 = edit_first_char_of_every_line(base_text_1);
18472    let text_2 = edit_first_char_of_every_line(base_text_2);
18473    let text_3 = edit_first_char_of_every_line(base_text_3);
18474
18475    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18476    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18477    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18478
18479    let multibuffer = cx.new(|cx| {
18480        let mut multibuffer = MultiBuffer::new(ReadWrite);
18481        multibuffer.push_excerpts(
18482            buffer_1.clone(),
18483            [
18484                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18485                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18486                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18487            ],
18488            cx,
18489        );
18490        multibuffer.push_excerpts(
18491            buffer_2.clone(),
18492            [
18493                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18494                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18495                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18496            ],
18497            cx,
18498        );
18499        multibuffer.push_excerpts(
18500            buffer_3.clone(),
18501            [
18502                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18503                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18504                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18505            ],
18506            cx,
18507        );
18508        multibuffer
18509    });
18510
18511    let fs = FakeFs::new(cx.executor());
18512    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
18513    let (editor, cx) = cx
18514        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
18515    editor.update_in(cx, |editor, _window, cx| {
18516        for (buffer, diff_base) in [
18517            (buffer_1.clone(), base_text_1),
18518            (buffer_2.clone(), base_text_2),
18519            (buffer_3.clone(), base_text_3),
18520        ] {
18521            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18522            editor
18523                .buffer
18524                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18525        }
18526    });
18527    cx.executor().run_until_parked();
18528
18529    editor.update_in(cx, |editor, window, cx| {
18530        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}");
18531        editor.select_all(&SelectAll, window, cx);
18532        editor.git_restore(&Default::default(), window, cx);
18533    });
18534    cx.executor().run_until_parked();
18535
18536    // When all ranges are selected, all buffer hunks are reverted.
18537    editor.update(cx, |editor, cx| {
18538        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");
18539    });
18540    buffer_1.update(cx, |buffer, _| {
18541        assert_eq!(buffer.text(), base_text_1);
18542    });
18543    buffer_2.update(cx, |buffer, _| {
18544        assert_eq!(buffer.text(), base_text_2);
18545    });
18546    buffer_3.update(cx, |buffer, _| {
18547        assert_eq!(buffer.text(), base_text_3);
18548    });
18549
18550    editor.update_in(cx, |editor, window, cx| {
18551        editor.undo(&Default::default(), window, cx);
18552    });
18553
18554    editor.update_in(cx, |editor, window, cx| {
18555        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18556            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
18557        });
18558        editor.git_restore(&Default::default(), window, cx);
18559    });
18560
18561    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
18562    // but not affect buffer_2 and its related excerpts.
18563    editor.update(cx, |editor, cx| {
18564        assert_eq!(
18565            editor.text(cx),
18566            "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}"
18567        );
18568    });
18569    buffer_1.update(cx, |buffer, _| {
18570        assert_eq!(buffer.text(), base_text_1);
18571    });
18572    buffer_2.update(cx, |buffer, _| {
18573        assert_eq!(
18574            buffer.text(),
18575            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
18576        );
18577    });
18578    buffer_3.update(cx, |buffer, _| {
18579        assert_eq!(
18580            buffer.text(),
18581            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
18582        );
18583    });
18584
18585    fn edit_first_char_of_every_line(text: &str) -> String {
18586        text.split('\n')
18587            .map(|line| format!("X{}", &line[1..]))
18588            .collect::<Vec<_>>()
18589            .join("\n")
18590    }
18591}
18592
18593#[gpui::test]
18594async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
18595    init_test(cx, |_| {});
18596
18597    let cols = 4;
18598    let rows = 10;
18599    let sample_text_1 = sample_text(rows, cols, 'a');
18600    assert_eq!(
18601        sample_text_1,
18602        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
18603    );
18604    let sample_text_2 = sample_text(rows, cols, 'l');
18605    assert_eq!(
18606        sample_text_2,
18607        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
18608    );
18609    let sample_text_3 = sample_text(rows, cols, 'v');
18610    assert_eq!(
18611        sample_text_3,
18612        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
18613    );
18614
18615    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
18616    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
18617    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
18618
18619    let multi_buffer = cx.new(|cx| {
18620        let mut multibuffer = MultiBuffer::new(ReadWrite);
18621        multibuffer.push_excerpts(
18622            buffer_1.clone(),
18623            [
18624                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18625                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18626                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18627            ],
18628            cx,
18629        );
18630        multibuffer.push_excerpts(
18631            buffer_2.clone(),
18632            [
18633                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18634                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18635                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18636            ],
18637            cx,
18638        );
18639        multibuffer.push_excerpts(
18640            buffer_3.clone(),
18641            [
18642                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18643                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18644                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18645            ],
18646            cx,
18647        );
18648        multibuffer
18649    });
18650
18651    let fs = FakeFs::new(cx.executor());
18652    fs.insert_tree(
18653        "/a",
18654        json!({
18655            "main.rs": sample_text_1,
18656            "other.rs": sample_text_2,
18657            "lib.rs": sample_text_3,
18658        }),
18659    )
18660    .await;
18661    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18662    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18663    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18664    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18665        Editor::new(
18666            EditorMode::full(),
18667            multi_buffer,
18668            Some(project.clone()),
18669            window,
18670            cx,
18671        )
18672    });
18673    let multibuffer_item_id = workspace
18674        .update(cx, |workspace, window, cx| {
18675            assert!(
18676                workspace.active_item(cx).is_none(),
18677                "active item should be None before the first item is added"
18678            );
18679            workspace.add_item_to_active_pane(
18680                Box::new(multi_buffer_editor.clone()),
18681                None,
18682                true,
18683                window,
18684                cx,
18685            );
18686            let active_item = workspace
18687                .active_item(cx)
18688                .expect("should have an active item after adding the multi buffer");
18689            assert!(
18690                !active_item.is_singleton(cx),
18691                "A multi buffer was expected to active after adding"
18692            );
18693            active_item.item_id()
18694        })
18695        .unwrap();
18696    cx.executor().run_until_parked();
18697
18698    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18699        editor.change_selections(
18700            SelectionEffects::scroll(Autoscroll::Next),
18701            window,
18702            cx,
18703            |s| s.select_ranges(Some(1..2)),
18704        );
18705        editor.open_excerpts(&OpenExcerpts, window, cx);
18706    });
18707    cx.executor().run_until_parked();
18708    let first_item_id = workspace
18709        .update(cx, |workspace, window, cx| {
18710            let active_item = workspace
18711                .active_item(cx)
18712                .expect("should have an active item after navigating into the 1st buffer");
18713            let first_item_id = active_item.item_id();
18714            assert_ne!(
18715                first_item_id, multibuffer_item_id,
18716                "Should navigate into the 1st buffer and activate it"
18717            );
18718            assert!(
18719                active_item.is_singleton(cx),
18720                "New active item should be a singleton buffer"
18721            );
18722            assert_eq!(
18723                active_item
18724                    .act_as::<Editor>(cx)
18725                    .expect("should have navigated into an editor for the 1st buffer")
18726                    .read(cx)
18727                    .text(cx),
18728                sample_text_1
18729            );
18730
18731            workspace
18732                .go_back(workspace.active_pane().downgrade(), window, cx)
18733                .detach_and_log_err(cx);
18734
18735            first_item_id
18736        })
18737        .unwrap();
18738    cx.executor().run_until_parked();
18739    workspace
18740        .update(cx, |workspace, _, cx| {
18741            let active_item = workspace
18742                .active_item(cx)
18743                .expect("should have an active item after navigating back");
18744            assert_eq!(
18745                active_item.item_id(),
18746                multibuffer_item_id,
18747                "Should navigate back to the multi buffer"
18748            );
18749            assert!(!active_item.is_singleton(cx));
18750        })
18751        .unwrap();
18752
18753    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18754        editor.change_selections(
18755            SelectionEffects::scroll(Autoscroll::Next),
18756            window,
18757            cx,
18758            |s| s.select_ranges(Some(39..40)),
18759        );
18760        editor.open_excerpts(&OpenExcerpts, window, cx);
18761    });
18762    cx.executor().run_until_parked();
18763    let second_item_id = workspace
18764        .update(cx, |workspace, window, cx| {
18765            let active_item = workspace
18766                .active_item(cx)
18767                .expect("should have an active item after navigating into the 2nd buffer");
18768            let second_item_id = active_item.item_id();
18769            assert_ne!(
18770                second_item_id, multibuffer_item_id,
18771                "Should navigate away from the multibuffer"
18772            );
18773            assert_ne!(
18774                second_item_id, first_item_id,
18775                "Should navigate into the 2nd buffer and activate it"
18776            );
18777            assert!(
18778                active_item.is_singleton(cx),
18779                "New active item should be a singleton buffer"
18780            );
18781            assert_eq!(
18782                active_item
18783                    .act_as::<Editor>(cx)
18784                    .expect("should have navigated into an editor")
18785                    .read(cx)
18786                    .text(cx),
18787                sample_text_2
18788            );
18789
18790            workspace
18791                .go_back(workspace.active_pane().downgrade(), window, cx)
18792                .detach_and_log_err(cx);
18793
18794            second_item_id
18795        })
18796        .unwrap();
18797    cx.executor().run_until_parked();
18798    workspace
18799        .update(cx, |workspace, _, cx| {
18800            let active_item = workspace
18801                .active_item(cx)
18802                .expect("should have an active item after navigating back from the 2nd buffer");
18803            assert_eq!(
18804                active_item.item_id(),
18805                multibuffer_item_id,
18806                "Should navigate back from the 2nd buffer to the multi buffer"
18807            );
18808            assert!(!active_item.is_singleton(cx));
18809        })
18810        .unwrap();
18811
18812    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18813        editor.change_selections(
18814            SelectionEffects::scroll(Autoscroll::Next),
18815            window,
18816            cx,
18817            |s| s.select_ranges(Some(70..70)),
18818        );
18819        editor.open_excerpts(&OpenExcerpts, window, cx);
18820    });
18821    cx.executor().run_until_parked();
18822    workspace
18823        .update(cx, |workspace, window, cx| {
18824            let active_item = workspace
18825                .active_item(cx)
18826                .expect("should have an active item after navigating into the 3rd buffer");
18827            let third_item_id = active_item.item_id();
18828            assert_ne!(
18829                third_item_id, multibuffer_item_id,
18830                "Should navigate into the 3rd buffer and activate it"
18831            );
18832            assert_ne!(third_item_id, first_item_id);
18833            assert_ne!(third_item_id, second_item_id);
18834            assert!(
18835                active_item.is_singleton(cx),
18836                "New active item should be a singleton buffer"
18837            );
18838            assert_eq!(
18839                active_item
18840                    .act_as::<Editor>(cx)
18841                    .expect("should have navigated into an editor")
18842                    .read(cx)
18843                    .text(cx),
18844                sample_text_3
18845            );
18846
18847            workspace
18848                .go_back(workspace.active_pane().downgrade(), window, cx)
18849                .detach_and_log_err(cx);
18850        })
18851        .unwrap();
18852    cx.executor().run_until_parked();
18853    workspace
18854        .update(cx, |workspace, _, cx| {
18855            let active_item = workspace
18856                .active_item(cx)
18857                .expect("should have an active item after navigating back from the 3rd buffer");
18858            assert_eq!(
18859                active_item.item_id(),
18860                multibuffer_item_id,
18861                "Should navigate back from the 3rd buffer to the multi buffer"
18862            );
18863            assert!(!active_item.is_singleton(cx));
18864        })
18865        .unwrap();
18866}
18867
18868#[gpui::test]
18869async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18870    init_test(cx, |_| {});
18871
18872    let mut cx = EditorTestContext::new(cx).await;
18873
18874    let diff_base = r#"
18875        use some::mod;
18876
18877        const A: u32 = 42;
18878
18879        fn main() {
18880            println!("hello");
18881
18882            println!("world");
18883        }
18884        "#
18885    .unindent();
18886
18887    cx.set_state(
18888        &r#"
18889        use some::modified;
18890
18891        ˇ
18892        fn main() {
18893            println!("hello there");
18894
18895            println!("around the");
18896            println!("world");
18897        }
18898        "#
18899        .unindent(),
18900    );
18901
18902    cx.set_head_text(&diff_base);
18903    executor.run_until_parked();
18904
18905    cx.update_editor(|editor, window, cx| {
18906        editor.go_to_next_hunk(&GoToHunk, window, cx);
18907        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18908    });
18909    executor.run_until_parked();
18910    cx.assert_state_with_diff(
18911        r#"
18912          use some::modified;
18913
18914
18915          fn main() {
18916        -     println!("hello");
18917        + ˇ    println!("hello there");
18918
18919              println!("around the");
18920              println!("world");
18921          }
18922        "#
18923        .unindent(),
18924    );
18925
18926    cx.update_editor(|editor, window, cx| {
18927        for _ in 0..2 {
18928            editor.go_to_next_hunk(&GoToHunk, window, cx);
18929            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18930        }
18931    });
18932    executor.run_until_parked();
18933    cx.assert_state_with_diff(
18934        r#"
18935        - use some::mod;
18936        + ˇuse some::modified;
18937
18938
18939          fn main() {
18940        -     println!("hello");
18941        +     println!("hello there");
18942
18943        +     println!("around the");
18944              println!("world");
18945          }
18946        "#
18947        .unindent(),
18948    );
18949
18950    cx.update_editor(|editor, window, cx| {
18951        editor.go_to_next_hunk(&GoToHunk, window, cx);
18952        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18953    });
18954    executor.run_until_parked();
18955    cx.assert_state_with_diff(
18956        r#"
18957        - use some::mod;
18958        + use some::modified;
18959
18960        - const A: u32 = 42;
18961          ˇ
18962          fn main() {
18963        -     println!("hello");
18964        +     println!("hello there");
18965
18966        +     println!("around the");
18967              println!("world");
18968          }
18969        "#
18970        .unindent(),
18971    );
18972
18973    cx.update_editor(|editor, window, cx| {
18974        editor.cancel(&Cancel, window, cx);
18975    });
18976
18977    cx.assert_state_with_diff(
18978        r#"
18979          use some::modified;
18980
18981          ˇ
18982          fn main() {
18983              println!("hello there");
18984
18985              println!("around the");
18986              println!("world");
18987          }
18988        "#
18989        .unindent(),
18990    );
18991}
18992
18993#[gpui::test]
18994async fn test_diff_base_change_with_expanded_diff_hunks(
18995    executor: BackgroundExecutor,
18996    cx: &mut TestAppContext,
18997) {
18998    init_test(cx, |_| {});
18999
19000    let mut cx = EditorTestContext::new(cx).await;
19001
19002    let diff_base = r#"
19003        use some::mod1;
19004        use some::mod2;
19005
19006        const A: u32 = 42;
19007        const B: u32 = 42;
19008        const C: u32 = 42;
19009
19010        fn main() {
19011            println!("hello");
19012
19013            println!("world");
19014        }
19015        "#
19016    .unindent();
19017
19018    cx.set_state(
19019        &r#"
19020        use some::mod2;
19021
19022        const A: u32 = 42;
19023        const C: u32 = 42;
19024
19025        fn main(ˇ) {
19026            //println!("hello");
19027
19028            println!("world");
19029            //
19030            //
19031        }
19032        "#
19033        .unindent(),
19034    );
19035
19036    cx.set_head_text(&diff_base);
19037    executor.run_until_parked();
19038
19039    cx.update_editor(|editor, window, cx| {
19040        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19041    });
19042    executor.run_until_parked();
19043    cx.assert_state_with_diff(
19044        r#"
19045        - use some::mod1;
19046          use some::mod2;
19047
19048          const A: u32 = 42;
19049        - const B: u32 = 42;
19050          const C: u32 = 42;
19051
19052          fn main(ˇ) {
19053        -     println!("hello");
19054        +     //println!("hello");
19055
19056              println!("world");
19057        +     //
19058        +     //
19059          }
19060        "#
19061        .unindent(),
19062    );
19063
19064    cx.set_head_text("new diff base!");
19065    executor.run_until_parked();
19066    cx.assert_state_with_diff(
19067        r#"
19068        - new diff base!
19069        + use some::mod2;
19070        +
19071        + const A: u32 = 42;
19072        + const C: u32 = 42;
19073        +
19074        + fn main(ˇ) {
19075        +     //println!("hello");
19076        +
19077        +     println!("world");
19078        +     //
19079        +     //
19080        + }
19081        "#
19082        .unindent(),
19083    );
19084}
19085
19086#[gpui::test]
19087async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19088    init_test(cx, |_| {});
19089
19090    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19091    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19092    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19093    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19094    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19095    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19096
19097    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19098    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19099    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19100
19101    let multi_buffer = cx.new(|cx| {
19102        let mut multibuffer = MultiBuffer::new(ReadWrite);
19103        multibuffer.push_excerpts(
19104            buffer_1.clone(),
19105            [
19106                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19107                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19108                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19109            ],
19110            cx,
19111        );
19112        multibuffer.push_excerpts(
19113            buffer_2.clone(),
19114            [
19115                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19116                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19117                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19118            ],
19119            cx,
19120        );
19121        multibuffer.push_excerpts(
19122            buffer_3.clone(),
19123            [
19124                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19125                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19126                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19127            ],
19128            cx,
19129        );
19130        multibuffer
19131    });
19132
19133    let editor =
19134        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19135    editor
19136        .update(cx, |editor, _window, cx| {
19137            for (buffer, diff_base) in [
19138                (buffer_1.clone(), file_1_old),
19139                (buffer_2.clone(), file_2_old),
19140                (buffer_3.clone(), file_3_old),
19141            ] {
19142                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19143                editor
19144                    .buffer
19145                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19146            }
19147        })
19148        .unwrap();
19149
19150    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19151    cx.run_until_parked();
19152
19153    cx.assert_editor_state(
19154        &"
19155            ˇaaa
19156            ccc
19157            ddd
19158
19159            ggg
19160            hhh
19161
19162
19163            lll
19164            mmm
19165            NNN
19166
19167            qqq
19168            rrr
19169
19170            uuu
19171            111
19172            222
19173            333
19174
19175            666
19176            777
19177
19178            000
19179            !!!"
19180        .unindent(),
19181    );
19182
19183    cx.update_editor(|editor, window, cx| {
19184        editor.select_all(&SelectAll, window, cx);
19185        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19186    });
19187    cx.executor().run_until_parked();
19188
19189    cx.assert_state_with_diff(
19190        "
19191            «aaa
19192          - bbb
19193            ccc
19194            ddd
19195
19196            ggg
19197            hhh
19198
19199
19200            lll
19201            mmm
19202          - nnn
19203          + NNN
19204
19205            qqq
19206            rrr
19207
19208            uuu
19209            111
19210            222
19211            333
19212
19213          + 666
19214            777
19215
19216            000
19217            !!!ˇ»"
19218            .unindent(),
19219    );
19220}
19221
19222#[gpui::test]
19223async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19224    init_test(cx, |_| {});
19225
19226    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19227    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19228
19229    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19230    let multi_buffer = cx.new(|cx| {
19231        let mut multibuffer = MultiBuffer::new(ReadWrite);
19232        multibuffer.push_excerpts(
19233            buffer.clone(),
19234            [
19235                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19236                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19237                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19238            ],
19239            cx,
19240        );
19241        multibuffer
19242    });
19243
19244    let editor =
19245        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19246    editor
19247        .update(cx, |editor, _window, cx| {
19248            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19249            editor
19250                .buffer
19251                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19252        })
19253        .unwrap();
19254
19255    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19256    cx.run_until_parked();
19257
19258    cx.update_editor(|editor, window, cx| {
19259        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19260    });
19261    cx.executor().run_until_parked();
19262
19263    // When the start of a hunk coincides with the start of its excerpt,
19264    // the hunk is expanded. When the start of a hunk is earlier than
19265    // the start of its excerpt, the hunk is not expanded.
19266    cx.assert_state_with_diff(
19267        "
19268            ˇaaa
19269          - bbb
19270          + BBB
19271
19272          - ddd
19273          - eee
19274          + DDD
19275          + EEE
19276            fff
19277
19278            iii
19279        "
19280        .unindent(),
19281    );
19282}
19283
19284#[gpui::test]
19285async fn test_edits_around_expanded_insertion_hunks(
19286    executor: BackgroundExecutor,
19287    cx: &mut TestAppContext,
19288) {
19289    init_test(cx, |_| {});
19290
19291    let mut cx = EditorTestContext::new(cx).await;
19292
19293    let diff_base = r#"
19294        use some::mod1;
19295        use some::mod2;
19296
19297        const A: u32 = 42;
19298
19299        fn main() {
19300            println!("hello");
19301
19302            println!("world");
19303        }
19304        "#
19305    .unindent();
19306    executor.run_until_parked();
19307    cx.set_state(
19308        &r#"
19309        use some::mod1;
19310        use some::mod2;
19311
19312        const A: u32 = 42;
19313        const B: u32 = 42;
19314        const C: u32 = 42;
19315        ˇ
19316
19317        fn main() {
19318            println!("hello");
19319
19320            println!("world");
19321        }
19322        "#
19323        .unindent(),
19324    );
19325
19326    cx.set_head_text(&diff_base);
19327    executor.run_until_parked();
19328
19329    cx.update_editor(|editor, window, cx| {
19330        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19331    });
19332    executor.run_until_parked();
19333
19334    cx.assert_state_with_diff(
19335        r#"
19336        use some::mod1;
19337        use some::mod2;
19338
19339        const A: u32 = 42;
19340      + const B: u32 = 42;
19341      + const C: u32 = 42;
19342      + ˇ
19343
19344        fn main() {
19345            println!("hello");
19346
19347            println!("world");
19348        }
19349      "#
19350        .unindent(),
19351    );
19352
19353    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19354    executor.run_until_parked();
19355
19356    cx.assert_state_with_diff(
19357        r#"
19358        use some::mod1;
19359        use some::mod2;
19360
19361        const A: u32 = 42;
19362      + const B: u32 = 42;
19363      + const C: u32 = 42;
19364      + const D: u32 = 42;
19365      + ˇ
19366
19367        fn main() {
19368            println!("hello");
19369
19370            println!("world");
19371        }
19372      "#
19373        .unindent(),
19374    );
19375
19376    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19377    executor.run_until_parked();
19378
19379    cx.assert_state_with_diff(
19380        r#"
19381        use some::mod1;
19382        use some::mod2;
19383
19384        const A: u32 = 42;
19385      + const B: u32 = 42;
19386      + const C: u32 = 42;
19387      + const D: u32 = 42;
19388      + const E: u32 = 42;
19389      + ˇ
19390
19391        fn main() {
19392            println!("hello");
19393
19394            println!("world");
19395        }
19396      "#
19397        .unindent(),
19398    );
19399
19400    cx.update_editor(|editor, window, cx| {
19401        editor.delete_line(&DeleteLine, window, cx);
19402    });
19403    executor.run_until_parked();
19404
19405    cx.assert_state_with_diff(
19406        r#"
19407        use some::mod1;
19408        use some::mod2;
19409
19410        const A: u32 = 42;
19411      + const B: u32 = 42;
19412      + const C: u32 = 42;
19413      + const D: u32 = 42;
19414      + const E: u32 = 42;
19415        ˇ
19416        fn main() {
19417            println!("hello");
19418
19419            println!("world");
19420        }
19421      "#
19422        .unindent(),
19423    );
19424
19425    cx.update_editor(|editor, window, cx| {
19426        editor.move_up(&MoveUp, window, cx);
19427        editor.delete_line(&DeleteLine, window, cx);
19428        editor.move_up(&MoveUp, window, cx);
19429        editor.delete_line(&DeleteLine, window, cx);
19430        editor.move_up(&MoveUp, window, cx);
19431        editor.delete_line(&DeleteLine, window, cx);
19432    });
19433    executor.run_until_parked();
19434    cx.assert_state_with_diff(
19435        r#"
19436        use some::mod1;
19437        use some::mod2;
19438
19439        const A: u32 = 42;
19440      + const B: u32 = 42;
19441        ˇ
19442        fn main() {
19443            println!("hello");
19444
19445            println!("world");
19446        }
19447      "#
19448        .unindent(),
19449    );
19450
19451    cx.update_editor(|editor, window, cx| {
19452        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19453        editor.delete_line(&DeleteLine, window, cx);
19454    });
19455    executor.run_until_parked();
19456    cx.assert_state_with_diff(
19457        r#"
19458        ˇ
19459        fn main() {
19460            println!("hello");
19461
19462            println!("world");
19463        }
19464      "#
19465        .unindent(),
19466    );
19467}
19468
19469#[gpui::test]
19470async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19471    init_test(cx, |_| {});
19472
19473    let mut cx = EditorTestContext::new(cx).await;
19474    cx.set_head_text(indoc! { "
19475        one
19476        two
19477        three
19478        four
19479        five
19480        "
19481    });
19482    cx.set_state(indoc! { "
19483        one
19484        ˇthree
19485        five
19486    "});
19487    cx.run_until_parked();
19488    cx.update_editor(|editor, window, cx| {
19489        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19490    });
19491    cx.assert_state_with_diff(
19492        indoc! { "
19493        one
19494      - two
19495        ˇthree
19496      - four
19497        five
19498    "}
19499        .to_string(),
19500    );
19501    cx.update_editor(|editor, window, cx| {
19502        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19503    });
19504
19505    cx.assert_state_with_diff(
19506        indoc! { "
19507        one
19508        ˇthree
19509        five
19510    "}
19511        .to_string(),
19512    );
19513
19514    cx.set_state(indoc! { "
19515        one
19516        ˇTWO
19517        three
19518        four
19519        five
19520    "});
19521    cx.run_until_parked();
19522    cx.update_editor(|editor, window, cx| {
19523        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19524    });
19525
19526    cx.assert_state_with_diff(
19527        indoc! { "
19528            one
19529          - two
19530          + ˇTWO
19531            three
19532            four
19533            five
19534        "}
19535        .to_string(),
19536    );
19537    cx.update_editor(|editor, window, cx| {
19538        editor.move_up(&Default::default(), window, cx);
19539        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19540    });
19541    cx.assert_state_with_diff(
19542        indoc! { "
19543            one
19544            ˇTWO
19545            three
19546            four
19547            five
19548        "}
19549        .to_string(),
19550    );
19551}
19552
19553#[gpui::test]
19554async fn test_edits_around_expanded_deletion_hunks(
19555    executor: BackgroundExecutor,
19556    cx: &mut TestAppContext,
19557) {
19558    init_test(cx, |_| {});
19559
19560    let mut cx = EditorTestContext::new(cx).await;
19561
19562    let diff_base = r#"
19563        use some::mod1;
19564        use some::mod2;
19565
19566        const A: u32 = 42;
19567        const B: u32 = 42;
19568        const C: u32 = 42;
19569
19570
19571        fn main() {
19572            println!("hello");
19573
19574            println!("world");
19575        }
19576    "#
19577    .unindent();
19578    executor.run_until_parked();
19579    cx.set_state(
19580        &r#"
19581        use some::mod1;
19582        use some::mod2;
19583
19584        ˇconst B: u32 = 42;
19585        const C: u32 = 42;
19586
19587
19588        fn main() {
19589            println!("hello");
19590
19591            println!("world");
19592        }
19593        "#
19594        .unindent(),
19595    );
19596
19597    cx.set_head_text(&diff_base);
19598    executor.run_until_parked();
19599
19600    cx.update_editor(|editor, window, cx| {
19601        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19602    });
19603    executor.run_until_parked();
19604
19605    cx.assert_state_with_diff(
19606        r#"
19607        use some::mod1;
19608        use some::mod2;
19609
19610      - const A: u32 = 42;
19611        ˇconst B: u32 = 42;
19612        const C: u32 = 42;
19613
19614
19615        fn main() {
19616            println!("hello");
19617
19618            println!("world");
19619        }
19620      "#
19621        .unindent(),
19622    );
19623
19624    cx.update_editor(|editor, window, cx| {
19625        editor.delete_line(&DeleteLine, window, cx);
19626    });
19627    executor.run_until_parked();
19628    cx.assert_state_with_diff(
19629        r#"
19630        use some::mod1;
19631        use some::mod2;
19632
19633      - const A: u32 = 42;
19634      - const B: u32 = 42;
19635        ˇconst C: u32 = 42;
19636
19637
19638        fn main() {
19639            println!("hello");
19640
19641            println!("world");
19642        }
19643      "#
19644        .unindent(),
19645    );
19646
19647    cx.update_editor(|editor, window, cx| {
19648        editor.delete_line(&DeleteLine, window, cx);
19649    });
19650    executor.run_until_parked();
19651    cx.assert_state_with_diff(
19652        r#"
19653        use some::mod1;
19654        use some::mod2;
19655
19656      - const A: u32 = 42;
19657      - const B: u32 = 42;
19658      - const C: u32 = 42;
19659        ˇ
19660
19661        fn main() {
19662            println!("hello");
19663
19664            println!("world");
19665        }
19666      "#
19667        .unindent(),
19668    );
19669
19670    cx.update_editor(|editor, window, cx| {
19671        editor.handle_input("replacement", window, cx);
19672    });
19673    executor.run_until_parked();
19674    cx.assert_state_with_diff(
19675        r#"
19676        use some::mod1;
19677        use some::mod2;
19678
19679      - const A: u32 = 42;
19680      - const B: u32 = 42;
19681      - const C: u32 = 42;
19682      -
19683      + replacementˇ
19684
19685        fn main() {
19686            println!("hello");
19687
19688            println!("world");
19689        }
19690      "#
19691        .unindent(),
19692    );
19693}
19694
19695#[gpui::test]
19696async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19697    init_test(cx, |_| {});
19698
19699    let mut cx = EditorTestContext::new(cx).await;
19700
19701    let base_text = r#"
19702        one
19703        two
19704        three
19705        four
19706        five
19707    "#
19708    .unindent();
19709    executor.run_until_parked();
19710    cx.set_state(
19711        &r#"
19712        one
19713        two
19714        fˇour
19715        five
19716        "#
19717        .unindent(),
19718    );
19719
19720    cx.set_head_text(&base_text);
19721    executor.run_until_parked();
19722
19723    cx.update_editor(|editor, window, cx| {
19724        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19725    });
19726    executor.run_until_parked();
19727
19728    cx.assert_state_with_diff(
19729        r#"
19730          one
19731          two
19732        - three
19733          fˇour
19734          five
19735        "#
19736        .unindent(),
19737    );
19738
19739    cx.update_editor(|editor, window, cx| {
19740        editor.backspace(&Backspace, window, cx);
19741        editor.backspace(&Backspace, window, cx);
19742    });
19743    executor.run_until_parked();
19744    cx.assert_state_with_diff(
19745        r#"
19746          one
19747          two
19748        - threeˇ
19749        - four
19750        + our
19751          five
19752        "#
19753        .unindent(),
19754    );
19755}
19756
19757#[gpui::test]
19758async fn test_edit_after_expanded_modification_hunk(
19759    executor: BackgroundExecutor,
19760    cx: &mut TestAppContext,
19761) {
19762    init_test(cx, |_| {});
19763
19764    let mut cx = EditorTestContext::new(cx).await;
19765
19766    let diff_base = r#"
19767        use some::mod1;
19768        use some::mod2;
19769
19770        const A: u32 = 42;
19771        const B: u32 = 42;
19772        const C: u32 = 42;
19773        const D: u32 = 42;
19774
19775
19776        fn main() {
19777            println!("hello");
19778
19779            println!("world");
19780        }"#
19781    .unindent();
19782
19783    cx.set_state(
19784        &r#"
19785        use some::mod1;
19786        use some::mod2;
19787
19788        const A: u32 = 42;
19789        const B: u32 = 42;
19790        const C: u32 = 43ˇ
19791        const D: u32 = 42;
19792
19793
19794        fn main() {
19795            println!("hello");
19796
19797            println!("world");
19798        }"#
19799        .unindent(),
19800    );
19801
19802    cx.set_head_text(&diff_base);
19803    executor.run_until_parked();
19804    cx.update_editor(|editor, window, cx| {
19805        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19806    });
19807    executor.run_until_parked();
19808
19809    cx.assert_state_with_diff(
19810        r#"
19811        use some::mod1;
19812        use some::mod2;
19813
19814        const A: u32 = 42;
19815        const B: u32 = 42;
19816      - const C: u32 = 42;
19817      + const C: u32 = 43ˇ
19818        const D: u32 = 42;
19819
19820
19821        fn main() {
19822            println!("hello");
19823
19824            println!("world");
19825        }"#
19826        .unindent(),
19827    );
19828
19829    cx.update_editor(|editor, window, cx| {
19830        editor.handle_input("\nnew_line\n", window, cx);
19831    });
19832    executor.run_until_parked();
19833
19834    cx.assert_state_with_diff(
19835        r#"
19836        use some::mod1;
19837        use some::mod2;
19838
19839        const A: u32 = 42;
19840        const B: u32 = 42;
19841      - const C: u32 = 42;
19842      + const C: u32 = 43
19843      + new_line
19844      + ˇ
19845        const D: u32 = 42;
19846
19847
19848        fn main() {
19849            println!("hello");
19850
19851            println!("world");
19852        }"#
19853        .unindent(),
19854    );
19855}
19856
19857#[gpui::test]
19858async fn test_stage_and_unstage_added_file_hunk(
19859    executor: BackgroundExecutor,
19860    cx: &mut TestAppContext,
19861) {
19862    init_test(cx, |_| {});
19863
19864    let mut cx = EditorTestContext::new(cx).await;
19865    cx.update_editor(|editor, _, cx| {
19866        editor.set_expand_all_diff_hunks(cx);
19867    });
19868
19869    let working_copy = r#"
19870            ˇfn main() {
19871                println!("hello, world!");
19872            }
19873        "#
19874    .unindent();
19875
19876    cx.set_state(&working_copy);
19877    executor.run_until_parked();
19878
19879    cx.assert_state_with_diff(
19880        r#"
19881            + ˇfn main() {
19882            +     println!("hello, world!");
19883            + }
19884        "#
19885        .unindent(),
19886    );
19887    cx.assert_index_text(None);
19888
19889    cx.update_editor(|editor, window, cx| {
19890        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19891    });
19892    executor.run_until_parked();
19893    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
19894    cx.assert_state_with_diff(
19895        r#"
19896            + ˇfn main() {
19897            +     println!("hello, world!");
19898            + }
19899        "#
19900        .unindent(),
19901    );
19902
19903    cx.update_editor(|editor, window, cx| {
19904        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19905    });
19906    executor.run_until_parked();
19907    cx.assert_index_text(None);
19908}
19909
19910async fn setup_indent_guides_editor(
19911    text: &str,
19912    cx: &mut TestAppContext,
19913) -> (BufferId, EditorTestContext) {
19914    init_test(cx, |_| {});
19915
19916    let mut cx = EditorTestContext::new(cx).await;
19917
19918    let buffer_id = cx.update_editor(|editor, window, cx| {
19919        editor.set_text(text, window, cx);
19920        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
19921
19922        buffer_ids[0]
19923    });
19924
19925    (buffer_id, cx)
19926}
19927
19928fn assert_indent_guides(
19929    range: Range<u32>,
19930    expected: Vec<IndentGuide>,
19931    active_indices: Option<Vec<usize>>,
19932    cx: &mut EditorTestContext,
19933) {
19934    let indent_guides = cx.update_editor(|editor, window, cx| {
19935        let snapshot = editor.snapshot(window, cx).display_snapshot;
19936        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
19937            editor,
19938            MultiBufferRow(range.start)..MultiBufferRow(range.end),
19939            true,
19940            &snapshot,
19941            cx,
19942        );
19943
19944        indent_guides.sort_by(|a, b| {
19945            a.depth.cmp(&b.depth).then(
19946                a.start_row
19947                    .cmp(&b.start_row)
19948                    .then(a.end_row.cmp(&b.end_row)),
19949            )
19950        });
19951        indent_guides
19952    });
19953
19954    if let Some(expected) = active_indices {
19955        let active_indices = cx.update_editor(|editor, window, cx| {
19956            let snapshot = editor.snapshot(window, cx).display_snapshot;
19957            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
19958        });
19959
19960        assert_eq!(
19961            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
19962            expected,
19963            "Active indent guide indices do not match"
19964        );
19965    }
19966
19967    assert_eq!(indent_guides, expected, "Indent guides do not match");
19968}
19969
19970fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
19971    IndentGuide {
19972        buffer_id,
19973        start_row: MultiBufferRow(start_row),
19974        end_row: MultiBufferRow(end_row),
19975        depth,
19976        tab_size: 4,
19977        settings: IndentGuideSettings {
19978            enabled: true,
19979            line_width: 1,
19980            active_line_width: 1,
19981            coloring: IndentGuideColoring::default(),
19982            background_coloring: IndentGuideBackgroundColoring::default(),
19983        },
19984    }
19985}
19986
19987#[gpui::test]
19988async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
19989    let (buffer_id, mut cx) = setup_indent_guides_editor(
19990        &"
19991        fn main() {
19992            let a = 1;
19993        }"
19994        .unindent(),
19995        cx,
19996    )
19997    .await;
19998
19999    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20000}
20001
20002#[gpui::test]
20003async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
20004    let (buffer_id, mut cx) = setup_indent_guides_editor(
20005        &"
20006        fn main() {
20007            let a = 1;
20008            let b = 2;
20009        }"
20010        .unindent(),
20011        cx,
20012    )
20013    .await;
20014
20015    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20016}
20017
20018#[gpui::test]
20019async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20020    let (buffer_id, mut cx) = setup_indent_guides_editor(
20021        &"
20022        fn main() {
20023            let a = 1;
20024            if a == 3 {
20025                let b = 2;
20026            } else {
20027                let c = 3;
20028            }
20029        }"
20030        .unindent(),
20031        cx,
20032    )
20033    .await;
20034
20035    assert_indent_guides(
20036        0..8,
20037        vec![
20038            indent_guide(buffer_id, 1, 6, 0),
20039            indent_guide(buffer_id, 3, 3, 1),
20040            indent_guide(buffer_id, 5, 5, 1),
20041        ],
20042        None,
20043        &mut cx,
20044    );
20045}
20046
20047#[gpui::test]
20048async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20049    let (buffer_id, mut cx) = setup_indent_guides_editor(
20050        &"
20051        fn main() {
20052            let a = 1;
20053                let b = 2;
20054            let c = 3;
20055        }"
20056        .unindent(),
20057        cx,
20058    )
20059    .await;
20060
20061    assert_indent_guides(
20062        0..5,
20063        vec![
20064            indent_guide(buffer_id, 1, 3, 0),
20065            indent_guide(buffer_id, 2, 2, 1),
20066        ],
20067        None,
20068        &mut cx,
20069    );
20070}
20071
20072#[gpui::test]
20073async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20074    let (buffer_id, mut cx) = setup_indent_guides_editor(
20075        &"
20076        fn main() {
20077            let a = 1;
20078
20079            let c = 3;
20080        }"
20081        .unindent(),
20082        cx,
20083    )
20084    .await;
20085
20086    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20087}
20088
20089#[gpui::test]
20090async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20091    let (buffer_id, mut cx) = setup_indent_guides_editor(
20092        &"
20093        fn main() {
20094            let a = 1;
20095
20096            let c = 3;
20097
20098            if a == 3 {
20099                let b = 2;
20100            } else {
20101                let c = 3;
20102            }
20103        }"
20104        .unindent(),
20105        cx,
20106    )
20107    .await;
20108
20109    assert_indent_guides(
20110        0..11,
20111        vec![
20112            indent_guide(buffer_id, 1, 9, 0),
20113            indent_guide(buffer_id, 6, 6, 1),
20114            indent_guide(buffer_id, 8, 8, 1),
20115        ],
20116        None,
20117        &mut cx,
20118    );
20119}
20120
20121#[gpui::test]
20122async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20123    let (buffer_id, mut cx) = setup_indent_guides_editor(
20124        &"
20125        fn main() {
20126            let a = 1;
20127
20128            let c = 3;
20129
20130            if a == 3 {
20131                let b = 2;
20132            } else {
20133                let c = 3;
20134            }
20135        }"
20136        .unindent(),
20137        cx,
20138    )
20139    .await;
20140
20141    assert_indent_guides(
20142        1..11,
20143        vec![
20144            indent_guide(buffer_id, 1, 9, 0),
20145            indent_guide(buffer_id, 6, 6, 1),
20146            indent_guide(buffer_id, 8, 8, 1),
20147        ],
20148        None,
20149        &mut cx,
20150    );
20151}
20152
20153#[gpui::test]
20154async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20155    let (buffer_id, mut cx) = setup_indent_guides_editor(
20156        &"
20157        fn main() {
20158            let a = 1;
20159
20160            let c = 3;
20161
20162            if a == 3 {
20163                let b = 2;
20164            } else {
20165                let c = 3;
20166            }
20167        }"
20168        .unindent(),
20169        cx,
20170    )
20171    .await;
20172
20173    assert_indent_guides(
20174        1..10,
20175        vec![
20176            indent_guide(buffer_id, 1, 9, 0),
20177            indent_guide(buffer_id, 6, 6, 1),
20178            indent_guide(buffer_id, 8, 8, 1),
20179        ],
20180        None,
20181        &mut cx,
20182    );
20183}
20184
20185#[gpui::test]
20186async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20187    let (buffer_id, mut cx) = setup_indent_guides_editor(
20188        &"
20189        fn main() {
20190            if a {
20191                b(
20192                    c,
20193                    d,
20194                )
20195            } else {
20196                e(
20197                    f
20198                )
20199            }
20200        }"
20201        .unindent(),
20202        cx,
20203    )
20204    .await;
20205
20206    assert_indent_guides(
20207        0..11,
20208        vec![
20209            indent_guide(buffer_id, 1, 10, 0),
20210            indent_guide(buffer_id, 2, 5, 1),
20211            indent_guide(buffer_id, 7, 9, 1),
20212            indent_guide(buffer_id, 3, 4, 2),
20213            indent_guide(buffer_id, 8, 8, 2),
20214        ],
20215        None,
20216        &mut cx,
20217    );
20218
20219    cx.update_editor(|editor, window, cx| {
20220        editor.fold_at(MultiBufferRow(2), window, cx);
20221        assert_eq!(
20222            editor.display_text(cx),
20223            "
20224            fn main() {
20225                if a {
20226                    b(⋯
20227                    )
20228                } else {
20229                    e(
20230                        f
20231                    )
20232                }
20233            }"
20234            .unindent()
20235        );
20236    });
20237
20238    assert_indent_guides(
20239        0..11,
20240        vec![
20241            indent_guide(buffer_id, 1, 10, 0),
20242            indent_guide(buffer_id, 2, 5, 1),
20243            indent_guide(buffer_id, 7, 9, 1),
20244            indent_guide(buffer_id, 8, 8, 2),
20245        ],
20246        None,
20247        &mut cx,
20248    );
20249}
20250
20251#[gpui::test]
20252async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20253    let (buffer_id, mut cx) = setup_indent_guides_editor(
20254        &"
20255        block1
20256            block2
20257                block3
20258                    block4
20259            block2
20260        block1
20261        block1"
20262            .unindent(),
20263        cx,
20264    )
20265    .await;
20266
20267    assert_indent_guides(
20268        1..10,
20269        vec![
20270            indent_guide(buffer_id, 1, 4, 0),
20271            indent_guide(buffer_id, 2, 3, 1),
20272            indent_guide(buffer_id, 3, 3, 2),
20273        ],
20274        None,
20275        &mut cx,
20276    );
20277}
20278
20279#[gpui::test]
20280async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20281    let (buffer_id, mut cx) = setup_indent_guides_editor(
20282        &"
20283        block1
20284            block2
20285                block3
20286
20287        block1
20288        block1"
20289            .unindent(),
20290        cx,
20291    )
20292    .await;
20293
20294    assert_indent_guides(
20295        0..6,
20296        vec![
20297            indent_guide(buffer_id, 1, 2, 0),
20298            indent_guide(buffer_id, 2, 2, 1),
20299        ],
20300        None,
20301        &mut cx,
20302    );
20303}
20304
20305#[gpui::test]
20306async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20307    let (buffer_id, mut cx) = setup_indent_guides_editor(
20308        &"
20309        function component() {
20310        \treturn (
20311        \t\t\t
20312        \t\t<div>
20313        \t\t\t<abc></abc>
20314        \t\t</div>
20315        \t)
20316        }"
20317        .unindent(),
20318        cx,
20319    )
20320    .await;
20321
20322    assert_indent_guides(
20323        0..8,
20324        vec![
20325            indent_guide(buffer_id, 1, 6, 0),
20326            indent_guide(buffer_id, 2, 5, 1),
20327            indent_guide(buffer_id, 4, 4, 2),
20328        ],
20329        None,
20330        &mut cx,
20331    );
20332}
20333
20334#[gpui::test]
20335async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20336    let (buffer_id, mut cx) = setup_indent_guides_editor(
20337        &"
20338        function component() {
20339        \treturn (
20340        \t
20341        \t\t<div>
20342        \t\t\t<abc></abc>
20343        \t\t</div>
20344        \t)
20345        }"
20346        .unindent(),
20347        cx,
20348    )
20349    .await;
20350
20351    assert_indent_guides(
20352        0..8,
20353        vec![
20354            indent_guide(buffer_id, 1, 6, 0),
20355            indent_guide(buffer_id, 2, 5, 1),
20356            indent_guide(buffer_id, 4, 4, 2),
20357        ],
20358        None,
20359        &mut cx,
20360    );
20361}
20362
20363#[gpui::test]
20364async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20365    let (buffer_id, mut cx) = setup_indent_guides_editor(
20366        &"
20367        block1
20368
20369
20370
20371            block2
20372        "
20373        .unindent(),
20374        cx,
20375    )
20376    .await;
20377
20378    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20379}
20380
20381#[gpui::test]
20382async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20383    let (buffer_id, mut cx) = setup_indent_guides_editor(
20384        &"
20385        def a:
20386        \tb = 3
20387        \tif True:
20388        \t\tc = 4
20389        \t\td = 5
20390        \tprint(b)
20391        "
20392        .unindent(),
20393        cx,
20394    )
20395    .await;
20396
20397    assert_indent_guides(
20398        0..6,
20399        vec![
20400            indent_guide(buffer_id, 1, 5, 0),
20401            indent_guide(buffer_id, 3, 4, 1),
20402        ],
20403        None,
20404        &mut cx,
20405    );
20406}
20407
20408#[gpui::test]
20409async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20410    let (buffer_id, mut cx) = setup_indent_guides_editor(
20411        &"
20412    fn main() {
20413        let a = 1;
20414    }"
20415        .unindent(),
20416        cx,
20417    )
20418    .await;
20419
20420    cx.update_editor(|editor, window, cx| {
20421        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20422            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20423        });
20424    });
20425
20426    assert_indent_guides(
20427        0..3,
20428        vec![indent_guide(buffer_id, 1, 1, 0)],
20429        Some(vec![0]),
20430        &mut cx,
20431    );
20432}
20433
20434#[gpui::test]
20435async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20436    let (buffer_id, mut cx) = setup_indent_guides_editor(
20437        &"
20438    fn main() {
20439        if 1 == 2 {
20440            let a = 1;
20441        }
20442    }"
20443        .unindent(),
20444        cx,
20445    )
20446    .await;
20447
20448    cx.update_editor(|editor, window, cx| {
20449        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20450            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20451        });
20452    });
20453
20454    assert_indent_guides(
20455        0..4,
20456        vec![
20457            indent_guide(buffer_id, 1, 3, 0),
20458            indent_guide(buffer_id, 2, 2, 1),
20459        ],
20460        Some(vec![1]),
20461        &mut cx,
20462    );
20463
20464    cx.update_editor(|editor, window, cx| {
20465        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20466            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20467        });
20468    });
20469
20470    assert_indent_guides(
20471        0..4,
20472        vec![
20473            indent_guide(buffer_id, 1, 3, 0),
20474            indent_guide(buffer_id, 2, 2, 1),
20475        ],
20476        Some(vec![1]),
20477        &mut cx,
20478    );
20479
20480    cx.update_editor(|editor, window, cx| {
20481        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20482            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20483        });
20484    });
20485
20486    assert_indent_guides(
20487        0..4,
20488        vec![
20489            indent_guide(buffer_id, 1, 3, 0),
20490            indent_guide(buffer_id, 2, 2, 1),
20491        ],
20492        Some(vec![0]),
20493        &mut cx,
20494    );
20495}
20496
20497#[gpui::test]
20498async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
20499    let (buffer_id, mut cx) = setup_indent_guides_editor(
20500        &"
20501    fn main() {
20502        let a = 1;
20503
20504        let b = 2;
20505    }"
20506        .unindent(),
20507        cx,
20508    )
20509    .await;
20510
20511    cx.update_editor(|editor, window, cx| {
20512        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20513            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20514        });
20515    });
20516
20517    assert_indent_guides(
20518        0..5,
20519        vec![indent_guide(buffer_id, 1, 3, 0)],
20520        Some(vec![0]),
20521        &mut cx,
20522    );
20523}
20524
20525#[gpui::test]
20526async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
20527    let (buffer_id, mut cx) = setup_indent_guides_editor(
20528        &"
20529    def m:
20530        a = 1
20531        pass"
20532            .unindent(),
20533        cx,
20534    )
20535    .await;
20536
20537    cx.update_editor(|editor, window, cx| {
20538        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20539            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20540        });
20541    });
20542
20543    assert_indent_guides(
20544        0..3,
20545        vec![indent_guide(buffer_id, 1, 2, 0)],
20546        Some(vec![0]),
20547        &mut cx,
20548    );
20549}
20550
20551#[gpui::test]
20552async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
20553    init_test(cx, |_| {});
20554    let mut cx = EditorTestContext::new(cx).await;
20555    let text = indoc! {
20556        "
20557        impl A {
20558            fn b() {
20559                0;
20560                3;
20561                5;
20562                6;
20563                7;
20564            }
20565        }
20566        "
20567    };
20568    let base_text = indoc! {
20569        "
20570        impl A {
20571            fn b() {
20572                0;
20573                1;
20574                2;
20575                3;
20576                4;
20577            }
20578            fn c() {
20579                5;
20580                6;
20581                7;
20582            }
20583        }
20584        "
20585    };
20586
20587    cx.update_editor(|editor, window, cx| {
20588        editor.set_text(text, window, cx);
20589
20590        editor.buffer().update(cx, |multibuffer, cx| {
20591            let buffer = multibuffer.as_singleton().unwrap();
20592            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
20593
20594            multibuffer.set_all_diff_hunks_expanded(cx);
20595            multibuffer.add_diff(diff, cx);
20596
20597            buffer.read(cx).remote_id()
20598        })
20599    });
20600    cx.run_until_parked();
20601
20602    cx.assert_state_with_diff(
20603        indoc! { "
20604          impl A {
20605              fn b() {
20606                  0;
20607        -         1;
20608        -         2;
20609                  3;
20610        -         4;
20611        -     }
20612        -     fn c() {
20613                  5;
20614                  6;
20615                  7;
20616              }
20617          }
20618          ˇ"
20619        }
20620        .to_string(),
20621    );
20622
20623    let mut actual_guides = cx.update_editor(|editor, window, cx| {
20624        editor
20625            .snapshot(window, cx)
20626            .buffer_snapshot
20627            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
20628            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
20629            .collect::<Vec<_>>()
20630    });
20631    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
20632    assert_eq!(
20633        actual_guides,
20634        vec![
20635            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
20636            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
20637            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
20638        ]
20639    );
20640}
20641
20642#[gpui::test]
20643async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20644    init_test(cx, |_| {});
20645    let mut cx = EditorTestContext::new(cx).await;
20646
20647    let diff_base = r#"
20648        a
20649        b
20650        c
20651        "#
20652    .unindent();
20653
20654    cx.set_state(
20655        &r#"
20656        ˇA
20657        b
20658        C
20659        "#
20660        .unindent(),
20661    );
20662    cx.set_head_text(&diff_base);
20663    cx.update_editor(|editor, window, cx| {
20664        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20665    });
20666    executor.run_until_parked();
20667
20668    let both_hunks_expanded = r#"
20669        - a
20670        + ˇA
20671          b
20672        - c
20673        + C
20674        "#
20675    .unindent();
20676
20677    cx.assert_state_with_diff(both_hunks_expanded.clone());
20678
20679    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20680        let snapshot = editor.snapshot(window, cx);
20681        let hunks = editor
20682            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20683            .collect::<Vec<_>>();
20684        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20685        let buffer_id = hunks[0].buffer_id;
20686        hunks
20687            .into_iter()
20688            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20689            .collect::<Vec<_>>()
20690    });
20691    assert_eq!(hunk_ranges.len(), 2);
20692
20693    cx.update_editor(|editor, _, cx| {
20694        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20695    });
20696    executor.run_until_parked();
20697
20698    let second_hunk_expanded = r#"
20699          ˇA
20700          b
20701        - c
20702        + C
20703        "#
20704    .unindent();
20705
20706    cx.assert_state_with_diff(second_hunk_expanded);
20707
20708    cx.update_editor(|editor, _, cx| {
20709        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20710    });
20711    executor.run_until_parked();
20712
20713    cx.assert_state_with_diff(both_hunks_expanded.clone());
20714
20715    cx.update_editor(|editor, _, cx| {
20716        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20717    });
20718    executor.run_until_parked();
20719
20720    let first_hunk_expanded = r#"
20721        - a
20722        + ˇA
20723          b
20724          C
20725        "#
20726    .unindent();
20727
20728    cx.assert_state_with_diff(first_hunk_expanded);
20729
20730    cx.update_editor(|editor, _, cx| {
20731        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20732    });
20733    executor.run_until_parked();
20734
20735    cx.assert_state_with_diff(both_hunks_expanded);
20736
20737    cx.set_state(
20738        &r#"
20739        ˇA
20740        b
20741        "#
20742        .unindent(),
20743    );
20744    cx.run_until_parked();
20745
20746    // TODO this cursor position seems bad
20747    cx.assert_state_with_diff(
20748        r#"
20749        - ˇa
20750        + A
20751          b
20752        "#
20753        .unindent(),
20754    );
20755
20756    cx.update_editor(|editor, window, cx| {
20757        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20758    });
20759
20760    cx.assert_state_with_diff(
20761        r#"
20762            - ˇa
20763            + A
20764              b
20765            - c
20766            "#
20767        .unindent(),
20768    );
20769
20770    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20771        let snapshot = editor.snapshot(window, cx);
20772        let hunks = editor
20773            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20774            .collect::<Vec<_>>();
20775        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20776        let buffer_id = hunks[0].buffer_id;
20777        hunks
20778            .into_iter()
20779            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20780            .collect::<Vec<_>>()
20781    });
20782    assert_eq!(hunk_ranges.len(), 2);
20783
20784    cx.update_editor(|editor, _, cx| {
20785        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20786    });
20787    executor.run_until_parked();
20788
20789    cx.assert_state_with_diff(
20790        r#"
20791        - ˇa
20792        + A
20793          b
20794        "#
20795        .unindent(),
20796    );
20797}
20798
20799#[gpui::test]
20800async fn test_toggle_deletion_hunk_at_start_of_file(
20801    executor: BackgroundExecutor,
20802    cx: &mut TestAppContext,
20803) {
20804    init_test(cx, |_| {});
20805    let mut cx = EditorTestContext::new(cx).await;
20806
20807    let diff_base = r#"
20808        a
20809        b
20810        c
20811        "#
20812    .unindent();
20813
20814    cx.set_state(
20815        &r#"
20816        ˇb
20817        c
20818        "#
20819        .unindent(),
20820    );
20821    cx.set_head_text(&diff_base);
20822    cx.update_editor(|editor, window, cx| {
20823        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20824    });
20825    executor.run_until_parked();
20826
20827    let hunk_expanded = r#"
20828        - a
20829          ˇb
20830          c
20831        "#
20832    .unindent();
20833
20834    cx.assert_state_with_diff(hunk_expanded.clone());
20835
20836    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20837        let snapshot = editor.snapshot(window, cx);
20838        let hunks = editor
20839            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20840            .collect::<Vec<_>>();
20841        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20842        let buffer_id = hunks[0].buffer_id;
20843        hunks
20844            .into_iter()
20845            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20846            .collect::<Vec<_>>()
20847    });
20848    assert_eq!(hunk_ranges.len(), 1);
20849
20850    cx.update_editor(|editor, _, cx| {
20851        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20852    });
20853    executor.run_until_parked();
20854
20855    let hunk_collapsed = r#"
20856          ˇb
20857          c
20858        "#
20859    .unindent();
20860
20861    cx.assert_state_with_diff(hunk_collapsed);
20862
20863    cx.update_editor(|editor, _, cx| {
20864        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20865    });
20866    executor.run_until_parked();
20867
20868    cx.assert_state_with_diff(hunk_expanded);
20869}
20870
20871#[gpui::test]
20872async fn test_display_diff_hunks(cx: &mut TestAppContext) {
20873    init_test(cx, |_| {});
20874
20875    let fs = FakeFs::new(cx.executor());
20876    fs.insert_tree(
20877        path!("/test"),
20878        json!({
20879            ".git": {},
20880            "file-1": "ONE\n",
20881            "file-2": "TWO\n",
20882            "file-3": "THREE\n",
20883        }),
20884    )
20885    .await;
20886
20887    fs.set_head_for_repo(
20888        path!("/test/.git").as_ref(),
20889        &[
20890            ("file-1", "one\n".into()),
20891            ("file-2", "two\n".into()),
20892            ("file-3", "three\n".into()),
20893        ],
20894        "deadbeef",
20895    );
20896
20897    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
20898    let mut buffers = vec![];
20899    for i in 1..=3 {
20900        let buffer = project
20901            .update(cx, |project, cx| {
20902                let path = format!(path!("/test/file-{}"), i);
20903                project.open_local_buffer(path, cx)
20904            })
20905            .await
20906            .unwrap();
20907        buffers.push(buffer);
20908    }
20909
20910    let multibuffer = cx.new(|cx| {
20911        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
20912        multibuffer.set_all_diff_hunks_expanded(cx);
20913        for buffer in &buffers {
20914            let snapshot = buffer.read(cx).snapshot();
20915            multibuffer.set_excerpts_for_path(
20916                PathKey::namespaced(
20917                    0,
20918                    buffer.read(cx).file().unwrap().path().as_unix_str().into(),
20919                ),
20920                buffer.clone(),
20921                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
20922                2,
20923                cx,
20924            );
20925        }
20926        multibuffer
20927    });
20928
20929    let editor = cx.add_window(|window, cx| {
20930        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
20931    });
20932    cx.run_until_parked();
20933
20934    let snapshot = editor
20935        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
20936        .unwrap();
20937    let hunks = snapshot
20938        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
20939        .map(|hunk| match hunk {
20940            DisplayDiffHunk::Unfolded {
20941                display_row_range, ..
20942            } => display_row_range,
20943            DisplayDiffHunk::Folded { .. } => unreachable!(),
20944        })
20945        .collect::<Vec<_>>();
20946    assert_eq!(
20947        hunks,
20948        [
20949            DisplayRow(2)..DisplayRow(4),
20950            DisplayRow(7)..DisplayRow(9),
20951            DisplayRow(12)..DisplayRow(14),
20952        ]
20953    );
20954}
20955
20956#[gpui::test]
20957async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
20958    init_test(cx, |_| {});
20959
20960    let mut cx = EditorTestContext::new(cx).await;
20961    cx.set_head_text(indoc! { "
20962        one
20963        two
20964        three
20965        four
20966        five
20967        "
20968    });
20969    cx.set_index_text(indoc! { "
20970        one
20971        two
20972        three
20973        four
20974        five
20975        "
20976    });
20977    cx.set_state(indoc! {"
20978        one
20979        TWO
20980        ˇTHREE
20981        FOUR
20982        five
20983    "});
20984    cx.run_until_parked();
20985    cx.update_editor(|editor, window, cx| {
20986        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20987    });
20988    cx.run_until_parked();
20989    cx.assert_index_text(Some(indoc! {"
20990        one
20991        TWO
20992        THREE
20993        FOUR
20994        five
20995    "}));
20996    cx.set_state(indoc! { "
20997        one
20998        TWO
20999        ˇTHREE-HUNDRED
21000        FOUR
21001        five
21002    "});
21003    cx.run_until_parked();
21004    cx.update_editor(|editor, window, cx| {
21005        let snapshot = editor.snapshot(window, cx);
21006        let hunks = editor
21007            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
21008            .collect::<Vec<_>>();
21009        assert_eq!(hunks.len(), 1);
21010        assert_eq!(
21011            hunks[0].status(),
21012            DiffHunkStatus {
21013                kind: DiffHunkStatusKind::Modified,
21014                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21015            }
21016        );
21017
21018        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21019    });
21020    cx.run_until_parked();
21021    cx.assert_index_text(Some(indoc! {"
21022        one
21023        TWO
21024        THREE-HUNDRED
21025        FOUR
21026        five
21027    "}));
21028}
21029
21030#[gpui::test]
21031fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21032    init_test(cx, |_| {});
21033
21034    let editor = cx.add_window(|window, cx| {
21035        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21036        build_editor(buffer, window, cx)
21037    });
21038
21039    let render_args = Arc::new(Mutex::new(None));
21040    let snapshot = editor
21041        .update(cx, |editor, window, cx| {
21042            let snapshot = editor.buffer().read(cx).snapshot(cx);
21043            let range =
21044                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21045
21046            struct RenderArgs {
21047                row: MultiBufferRow,
21048                folded: bool,
21049                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21050            }
21051
21052            let crease = Crease::inline(
21053                range,
21054                FoldPlaceholder::test(),
21055                {
21056                    let toggle_callback = render_args.clone();
21057                    move |row, folded, callback, _window, _cx| {
21058                        *toggle_callback.lock() = Some(RenderArgs {
21059                            row,
21060                            folded,
21061                            callback,
21062                        });
21063                        div()
21064                    }
21065                },
21066                |_row, _folded, _window, _cx| div(),
21067            );
21068
21069            editor.insert_creases(Some(crease), cx);
21070            let snapshot = editor.snapshot(window, cx);
21071            let _div =
21072                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21073            snapshot
21074        })
21075        .unwrap();
21076
21077    let render_args = render_args.lock().take().unwrap();
21078    assert_eq!(render_args.row, MultiBufferRow(1));
21079    assert!(!render_args.folded);
21080    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21081
21082    cx.update_window(*editor, |_, window, cx| {
21083        (render_args.callback)(true, window, cx)
21084    })
21085    .unwrap();
21086    let snapshot = editor
21087        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21088        .unwrap();
21089    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21090
21091    cx.update_window(*editor, |_, window, cx| {
21092        (render_args.callback)(false, window, cx)
21093    })
21094    .unwrap();
21095    let snapshot = editor
21096        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21097        .unwrap();
21098    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21099}
21100
21101#[gpui::test]
21102async fn test_input_text(cx: &mut TestAppContext) {
21103    init_test(cx, |_| {});
21104    let mut cx = EditorTestContext::new(cx).await;
21105
21106    cx.set_state(
21107        &r#"ˇone
21108        two
21109
21110        three
21111        fourˇ
21112        five
21113
21114        siˇx"#
21115            .unindent(),
21116    );
21117
21118    cx.dispatch_action(HandleInput(String::new()));
21119    cx.assert_editor_state(
21120        &r#"ˇone
21121        two
21122
21123        three
21124        fourˇ
21125        five
21126
21127        siˇx"#
21128            .unindent(),
21129    );
21130
21131    cx.dispatch_action(HandleInput("AAAA".to_string()));
21132    cx.assert_editor_state(
21133        &r#"AAAAˇone
21134        two
21135
21136        three
21137        fourAAAAˇ
21138        five
21139
21140        siAAAAˇx"#
21141            .unindent(),
21142    );
21143}
21144
21145#[gpui::test]
21146async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
21147    init_test(cx, |_| {});
21148
21149    let mut cx = EditorTestContext::new(cx).await;
21150    cx.set_state(
21151        r#"let foo = 1;
21152let foo = 2;
21153let foo = 3;
21154let fooˇ = 4;
21155let foo = 5;
21156let foo = 6;
21157let foo = 7;
21158let foo = 8;
21159let foo = 9;
21160let foo = 10;
21161let foo = 11;
21162let foo = 12;
21163let foo = 13;
21164let foo = 14;
21165let foo = 15;"#,
21166    );
21167
21168    cx.update_editor(|e, window, cx| {
21169        assert_eq!(
21170            e.next_scroll_position,
21171            NextScrollCursorCenterTopBottom::Center,
21172            "Default next scroll direction is center",
21173        );
21174
21175        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21176        assert_eq!(
21177            e.next_scroll_position,
21178            NextScrollCursorCenterTopBottom::Top,
21179            "After center, next scroll direction should be top",
21180        );
21181
21182        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21183        assert_eq!(
21184            e.next_scroll_position,
21185            NextScrollCursorCenterTopBottom::Bottom,
21186            "After top, next scroll direction should be bottom",
21187        );
21188
21189        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21190        assert_eq!(
21191            e.next_scroll_position,
21192            NextScrollCursorCenterTopBottom::Center,
21193            "After bottom, scrolling should start over",
21194        );
21195
21196        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21197        assert_eq!(
21198            e.next_scroll_position,
21199            NextScrollCursorCenterTopBottom::Top,
21200            "Scrolling continues if retriggered fast enough"
21201        );
21202    });
21203
21204    cx.executor()
21205        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
21206    cx.executor().run_until_parked();
21207    cx.update_editor(|e, _, _| {
21208        assert_eq!(
21209            e.next_scroll_position,
21210            NextScrollCursorCenterTopBottom::Center,
21211            "If scrolling is not triggered fast enough, it should reset"
21212        );
21213    });
21214}
21215
21216#[gpui::test]
21217async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
21218    init_test(cx, |_| {});
21219    let mut cx = EditorLspTestContext::new_rust(
21220        lsp::ServerCapabilities {
21221            definition_provider: Some(lsp::OneOf::Left(true)),
21222            references_provider: Some(lsp::OneOf::Left(true)),
21223            ..lsp::ServerCapabilities::default()
21224        },
21225        cx,
21226    )
21227    .await;
21228
21229    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21230        let go_to_definition = cx
21231            .lsp
21232            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21233                move |params, _| async move {
21234                    if empty_go_to_definition {
21235                        Ok(None)
21236                    } else {
21237                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21238                            uri: params.text_document_position_params.text_document.uri,
21239                            range: lsp::Range::new(
21240                                lsp::Position::new(4, 3),
21241                                lsp::Position::new(4, 6),
21242                            ),
21243                        })))
21244                    }
21245                },
21246            );
21247        let references = cx
21248            .lsp
21249            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21250                Ok(Some(vec![lsp::Location {
21251                    uri: params.text_document_position.text_document.uri,
21252                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21253                }]))
21254            });
21255        (go_to_definition, references)
21256    };
21257
21258    cx.set_state(
21259        &r#"fn one() {
21260            let mut a = ˇtwo();
21261        }
21262
21263        fn two() {}"#
21264            .unindent(),
21265    );
21266    set_up_lsp_handlers(false, &mut cx);
21267    let navigated = cx
21268        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21269        .await
21270        .expect("Failed to navigate to definition");
21271    assert_eq!(
21272        navigated,
21273        Navigated::Yes,
21274        "Should have navigated to definition from the GetDefinition response"
21275    );
21276    cx.assert_editor_state(
21277        &r#"fn one() {
21278            let mut a = two();
21279        }
21280
21281        fn «twoˇ»() {}"#
21282            .unindent(),
21283    );
21284
21285    let editors = cx.update_workspace(|workspace, _, cx| {
21286        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21287    });
21288    cx.update_editor(|_, _, test_editor_cx| {
21289        assert_eq!(
21290            editors.len(),
21291            1,
21292            "Initially, only one, test, editor should be open in the workspace"
21293        );
21294        assert_eq!(
21295            test_editor_cx.entity(),
21296            editors.last().expect("Asserted len is 1").clone()
21297        );
21298    });
21299
21300    set_up_lsp_handlers(true, &mut cx);
21301    let navigated = cx
21302        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21303        .await
21304        .expect("Failed to navigate to lookup references");
21305    assert_eq!(
21306        navigated,
21307        Navigated::Yes,
21308        "Should have navigated to references as a fallback after empty GoToDefinition response"
21309    );
21310    // We should not change the selections in the existing file,
21311    // if opening another milti buffer with the references
21312    cx.assert_editor_state(
21313        &r#"fn one() {
21314            let mut a = two();
21315        }
21316
21317        fn «twoˇ»() {}"#
21318            .unindent(),
21319    );
21320    let editors = cx.update_workspace(|workspace, _, cx| {
21321        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21322    });
21323    cx.update_editor(|_, _, test_editor_cx| {
21324        assert_eq!(
21325            editors.len(),
21326            2,
21327            "After falling back to references search, we open a new editor with the results"
21328        );
21329        let references_fallback_text = editors
21330            .into_iter()
21331            .find(|new_editor| *new_editor != test_editor_cx.entity())
21332            .expect("Should have one non-test editor now")
21333            .read(test_editor_cx)
21334            .text(test_editor_cx);
21335        assert_eq!(
21336            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21337            "Should use the range from the references response and not the GoToDefinition one"
21338        );
21339    });
21340}
21341
21342#[gpui::test]
21343async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21344    init_test(cx, |_| {});
21345    cx.update(|cx| {
21346        let mut editor_settings = EditorSettings::get_global(cx).clone();
21347        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21348        EditorSettings::override_global(editor_settings, cx);
21349    });
21350    let mut cx = EditorLspTestContext::new_rust(
21351        lsp::ServerCapabilities {
21352            definition_provider: Some(lsp::OneOf::Left(true)),
21353            references_provider: Some(lsp::OneOf::Left(true)),
21354            ..lsp::ServerCapabilities::default()
21355        },
21356        cx,
21357    )
21358    .await;
21359    let original_state = r#"fn one() {
21360        let mut a = ˇtwo();
21361    }
21362
21363    fn two() {}"#
21364        .unindent();
21365    cx.set_state(&original_state);
21366
21367    let mut go_to_definition = cx
21368        .lsp
21369        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21370            move |_, _| async move { Ok(None) },
21371        );
21372    let _references = cx
21373        .lsp
21374        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21375            panic!("Should not call for references with no go to definition fallback")
21376        });
21377
21378    let navigated = cx
21379        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21380        .await
21381        .expect("Failed to navigate to lookup references");
21382    go_to_definition
21383        .next()
21384        .await
21385        .expect("Should have called the go_to_definition handler");
21386
21387    assert_eq!(
21388        navigated,
21389        Navigated::No,
21390        "Should have navigated to references as a fallback after empty GoToDefinition response"
21391    );
21392    cx.assert_editor_state(&original_state);
21393    let editors = cx.update_workspace(|workspace, _, cx| {
21394        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21395    });
21396    cx.update_editor(|_, _, _| {
21397        assert_eq!(
21398            editors.len(),
21399            1,
21400            "After unsuccessful fallback, no other editor should have been opened"
21401        );
21402    });
21403}
21404
21405#[gpui::test]
21406async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
21407    init_test(cx, |_| {});
21408    let mut cx = EditorLspTestContext::new_rust(
21409        lsp::ServerCapabilities {
21410            references_provider: Some(lsp::OneOf::Left(true)),
21411            ..lsp::ServerCapabilities::default()
21412        },
21413        cx,
21414    )
21415    .await;
21416
21417    cx.set_state(
21418        &r#"
21419        fn one() {
21420            let mut a = two();
21421        }
21422
21423        fn ˇtwo() {}"#
21424            .unindent(),
21425    );
21426    cx.lsp
21427        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21428            Ok(Some(vec![
21429                lsp::Location {
21430                    uri: params.text_document_position.text_document.uri.clone(),
21431                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21432                },
21433                lsp::Location {
21434                    uri: params.text_document_position.text_document.uri,
21435                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
21436                },
21437            ]))
21438        });
21439    let navigated = cx
21440        .update_editor(|editor, window, cx| {
21441            editor.find_all_references(&FindAllReferences, window, cx)
21442        })
21443        .unwrap()
21444        .await
21445        .expect("Failed to navigate to references");
21446    assert_eq!(
21447        navigated,
21448        Navigated::Yes,
21449        "Should have navigated to references from the FindAllReferences response"
21450    );
21451    cx.assert_editor_state(
21452        &r#"fn one() {
21453            let mut a = two();
21454        }
21455
21456        fn ˇtwo() {}"#
21457            .unindent(),
21458    );
21459
21460    let editors = cx.update_workspace(|workspace, _, cx| {
21461        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21462    });
21463    cx.update_editor(|_, _, _| {
21464        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
21465    });
21466
21467    cx.set_state(
21468        &r#"fn one() {
21469            let mut a = ˇtwo();
21470        }
21471
21472        fn two() {}"#
21473            .unindent(),
21474    );
21475    let navigated = cx
21476        .update_editor(|editor, window, cx| {
21477            editor.find_all_references(&FindAllReferences, window, cx)
21478        })
21479        .unwrap()
21480        .await
21481        .expect("Failed to navigate to references");
21482    assert_eq!(
21483        navigated,
21484        Navigated::Yes,
21485        "Should have navigated to references from the FindAllReferences response"
21486    );
21487    cx.assert_editor_state(
21488        &r#"fn one() {
21489            let mut a = ˇtwo();
21490        }
21491
21492        fn two() {}"#
21493            .unindent(),
21494    );
21495    let editors = cx.update_workspace(|workspace, _, cx| {
21496        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21497    });
21498    cx.update_editor(|_, _, _| {
21499        assert_eq!(
21500            editors.len(),
21501            2,
21502            "should have re-used the previous multibuffer"
21503        );
21504    });
21505
21506    cx.set_state(
21507        &r#"fn one() {
21508            let mut a = ˇtwo();
21509        }
21510        fn three() {}
21511        fn two() {}"#
21512            .unindent(),
21513    );
21514    cx.lsp
21515        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21516            Ok(Some(vec![
21517                lsp::Location {
21518                    uri: params.text_document_position.text_document.uri.clone(),
21519                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21520                },
21521                lsp::Location {
21522                    uri: params.text_document_position.text_document.uri,
21523                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
21524                },
21525            ]))
21526        });
21527    let navigated = cx
21528        .update_editor(|editor, window, cx| {
21529            editor.find_all_references(&FindAllReferences, window, cx)
21530        })
21531        .unwrap()
21532        .await
21533        .expect("Failed to navigate to references");
21534    assert_eq!(
21535        navigated,
21536        Navigated::Yes,
21537        "Should have navigated to references from the FindAllReferences response"
21538    );
21539    cx.assert_editor_state(
21540        &r#"fn one() {
21541                let mut a = ˇtwo();
21542            }
21543            fn three() {}
21544            fn two() {}"#
21545            .unindent(),
21546    );
21547    let editors = cx.update_workspace(|workspace, _, cx| {
21548        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21549    });
21550    cx.update_editor(|_, _, _| {
21551        assert_eq!(
21552            editors.len(),
21553            3,
21554            "should have used a new multibuffer as offsets changed"
21555        );
21556    });
21557}
21558#[gpui::test]
21559async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
21560    init_test(cx, |_| {});
21561
21562    let language = Arc::new(Language::new(
21563        LanguageConfig::default(),
21564        Some(tree_sitter_rust::LANGUAGE.into()),
21565    ));
21566
21567    let text = r#"
21568        #[cfg(test)]
21569        mod tests() {
21570            #[test]
21571            fn runnable_1() {
21572                let a = 1;
21573            }
21574
21575            #[test]
21576            fn runnable_2() {
21577                let a = 1;
21578                let b = 2;
21579            }
21580        }
21581    "#
21582    .unindent();
21583
21584    let fs = FakeFs::new(cx.executor());
21585    fs.insert_file("/file.rs", Default::default()).await;
21586
21587    let project = Project::test(fs, ["/a".as_ref()], cx).await;
21588    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21589    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21590    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
21591    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
21592
21593    let editor = cx.new_window_entity(|window, cx| {
21594        Editor::new(
21595            EditorMode::full(),
21596            multi_buffer,
21597            Some(project.clone()),
21598            window,
21599            cx,
21600        )
21601    });
21602
21603    editor.update_in(cx, |editor, window, cx| {
21604        let snapshot = editor.buffer().read(cx).snapshot(cx);
21605        editor.tasks.insert(
21606            (buffer.read(cx).remote_id(), 3),
21607            RunnableTasks {
21608                templates: vec![],
21609                offset: snapshot.anchor_before(43),
21610                column: 0,
21611                extra_variables: HashMap::default(),
21612                context_range: BufferOffset(43)..BufferOffset(85),
21613            },
21614        );
21615        editor.tasks.insert(
21616            (buffer.read(cx).remote_id(), 8),
21617            RunnableTasks {
21618                templates: vec![],
21619                offset: snapshot.anchor_before(86),
21620                column: 0,
21621                extra_variables: HashMap::default(),
21622                context_range: BufferOffset(86)..BufferOffset(191),
21623            },
21624        );
21625
21626        // Test finding task when cursor is inside function body
21627        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21628            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
21629        });
21630        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21631        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
21632
21633        // Test finding task when cursor is on function name
21634        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21635            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
21636        });
21637        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21638        assert_eq!(row, 8, "Should find task when cursor is on function name");
21639    });
21640}
21641
21642#[gpui::test]
21643async fn test_folding_buffers(cx: &mut TestAppContext) {
21644    init_test(cx, |_| {});
21645
21646    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21647    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
21648    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
21649
21650    let fs = FakeFs::new(cx.executor());
21651    fs.insert_tree(
21652        path!("/a"),
21653        json!({
21654            "first.rs": sample_text_1,
21655            "second.rs": sample_text_2,
21656            "third.rs": sample_text_3,
21657        }),
21658    )
21659    .await;
21660    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21661    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21662    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21663    let worktree = project.update(cx, |project, cx| {
21664        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21665        assert_eq!(worktrees.len(), 1);
21666        worktrees.pop().unwrap()
21667    });
21668    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21669
21670    let buffer_1 = project
21671        .update(cx, |project, cx| {
21672            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
21673        })
21674        .await
21675        .unwrap();
21676    let buffer_2 = project
21677        .update(cx, |project, cx| {
21678            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
21679        })
21680        .await
21681        .unwrap();
21682    let buffer_3 = project
21683        .update(cx, |project, cx| {
21684            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
21685        })
21686        .await
21687        .unwrap();
21688
21689    let multi_buffer = cx.new(|cx| {
21690        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21691        multi_buffer.push_excerpts(
21692            buffer_1.clone(),
21693            [
21694                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21695                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21696                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21697            ],
21698            cx,
21699        );
21700        multi_buffer.push_excerpts(
21701            buffer_2.clone(),
21702            [
21703                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21704                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21705                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21706            ],
21707            cx,
21708        );
21709        multi_buffer.push_excerpts(
21710            buffer_3.clone(),
21711            [
21712                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21713                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21714                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21715            ],
21716            cx,
21717        );
21718        multi_buffer
21719    });
21720    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21721        Editor::new(
21722            EditorMode::full(),
21723            multi_buffer.clone(),
21724            Some(project.clone()),
21725            window,
21726            cx,
21727        )
21728    });
21729
21730    assert_eq!(
21731        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21732        "\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",
21733    );
21734
21735    multi_buffer_editor.update(cx, |editor, cx| {
21736        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21737    });
21738    assert_eq!(
21739        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21740        "\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",
21741        "After folding the first buffer, its text should not be displayed"
21742    );
21743
21744    multi_buffer_editor.update(cx, |editor, cx| {
21745        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21746    });
21747    assert_eq!(
21748        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21749        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
21750        "After folding the second buffer, its text should not be displayed"
21751    );
21752
21753    multi_buffer_editor.update(cx, |editor, cx| {
21754        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21755    });
21756    assert_eq!(
21757        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21758        "\n\n\n\n\n",
21759        "After folding the third buffer, its text should not be displayed"
21760    );
21761
21762    // Emulate selection inside the fold logic, that should work
21763    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21764        editor
21765            .snapshot(window, cx)
21766            .next_line_boundary(Point::new(0, 4));
21767    });
21768
21769    multi_buffer_editor.update(cx, |editor, cx| {
21770        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21771    });
21772    assert_eq!(
21773        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21774        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21775        "After unfolding the second buffer, its text should be displayed"
21776    );
21777
21778    // Typing inside of buffer 1 causes that buffer to be unfolded.
21779    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21780        assert_eq!(
21781            multi_buffer
21782                .read(cx)
21783                .snapshot(cx)
21784                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
21785                .collect::<String>(),
21786            "bbbb"
21787        );
21788        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21789            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
21790        });
21791        editor.handle_input("B", window, cx);
21792    });
21793
21794    assert_eq!(
21795        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21796        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21797        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
21798    );
21799
21800    multi_buffer_editor.update(cx, |editor, cx| {
21801        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21802    });
21803    assert_eq!(
21804        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21805        "\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",
21806        "After unfolding the all buffers, all original text should be displayed"
21807    );
21808}
21809
21810#[gpui::test]
21811async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
21812    init_test(cx, |_| {});
21813
21814    let sample_text_1 = "1111\n2222\n3333".to_string();
21815    let sample_text_2 = "4444\n5555\n6666".to_string();
21816    let sample_text_3 = "7777\n8888\n9999".to_string();
21817
21818    let fs = FakeFs::new(cx.executor());
21819    fs.insert_tree(
21820        path!("/a"),
21821        json!({
21822            "first.rs": sample_text_1,
21823            "second.rs": sample_text_2,
21824            "third.rs": sample_text_3,
21825        }),
21826    )
21827    .await;
21828    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21829    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21830    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21831    let worktree = project.update(cx, |project, cx| {
21832        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21833        assert_eq!(worktrees.len(), 1);
21834        worktrees.pop().unwrap()
21835    });
21836    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21837
21838    let buffer_1 = project
21839        .update(cx, |project, cx| {
21840            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
21841        })
21842        .await
21843        .unwrap();
21844    let buffer_2 = project
21845        .update(cx, |project, cx| {
21846            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
21847        })
21848        .await
21849        .unwrap();
21850    let buffer_3 = project
21851        .update(cx, |project, cx| {
21852            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
21853        })
21854        .await
21855        .unwrap();
21856
21857    let multi_buffer = cx.new(|cx| {
21858        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21859        multi_buffer.push_excerpts(
21860            buffer_1.clone(),
21861            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21862            cx,
21863        );
21864        multi_buffer.push_excerpts(
21865            buffer_2.clone(),
21866            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21867            cx,
21868        );
21869        multi_buffer.push_excerpts(
21870            buffer_3.clone(),
21871            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21872            cx,
21873        );
21874        multi_buffer
21875    });
21876
21877    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21878        Editor::new(
21879            EditorMode::full(),
21880            multi_buffer,
21881            Some(project.clone()),
21882            window,
21883            cx,
21884        )
21885    });
21886
21887    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
21888    assert_eq!(
21889        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21890        full_text,
21891    );
21892
21893    multi_buffer_editor.update(cx, |editor, cx| {
21894        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21895    });
21896    assert_eq!(
21897        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21898        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
21899        "After folding the first buffer, its text should not be displayed"
21900    );
21901
21902    multi_buffer_editor.update(cx, |editor, cx| {
21903        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21904    });
21905
21906    assert_eq!(
21907        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21908        "\n\n\n\n\n\n7777\n8888\n9999",
21909        "After folding the second buffer, its text should not be displayed"
21910    );
21911
21912    multi_buffer_editor.update(cx, |editor, cx| {
21913        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21914    });
21915    assert_eq!(
21916        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21917        "\n\n\n\n\n",
21918        "After folding the third buffer, its text should not be displayed"
21919    );
21920
21921    multi_buffer_editor.update(cx, |editor, cx| {
21922        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21923    });
21924    assert_eq!(
21925        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21926        "\n\n\n\n4444\n5555\n6666\n\n",
21927        "After unfolding the second buffer, its text should be displayed"
21928    );
21929
21930    multi_buffer_editor.update(cx, |editor, cx| {
21931        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
21932    });
21933    assert_eq!(
21934        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21935        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
21936        "After unfolding the first buffer, its text should be displayed"
21937    );
21938
21939    multi_buffer_editor.update(cx, |editor, cx| {
21940        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21941    });
21942    assert_eq!(
21943        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21944        full_text,
21945        "After unfolding all buffers, all original text should be displayed"
21946    );
21947}
21948
21949#[gpui::test]
21950async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
21951    init_test(cx, |_| {});
21952
21953    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21954
21955    let fs = FakeFs::new(cx.executor());
21956    fs.insert_tree(
21957        path!("/a"),
21958        json!({
21959            "main.rs": sample_text,
21960        }),
21961    )
21962    .await;
21963    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21964    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21965    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21966    let worktree = project.update(cx, |project, cx| {
21967        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21968        assert_eq!(worktrees.len(), 1);
21969        worktrees.pop().unwrap()
21970    });
21971    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21972
21973    let buffer_1 = project
21974        .update(cx, |project, cx| {
21975            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
21976        })
21977        .await
21978        .unwrap();
21979
21980    let multi_buffer = cx.new(|cx| {
21981        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21982        multi_buffer.push_excerpts(
21983            buffer_1.clone(),
21984            [ExcerptRange::new(
21985                Point::new(0, 0)
21986                    ..Point::new(
21987                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
21988                        0,
21989                    ),
21990            )],
21991            cx,
21992        );
21993        multi_buffer
21994    });
21995    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21996        Editor::new(
21997            EditorMode::full(),
21998            multi_buffer,
21999            Some(project.clone()),
22000            window,
22001            cx,
22002        )
22003    });
22004
22005    let selection_range = Point::new(1, 0)..Point::new(2, 0);
22006    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22007        enum TestHighlight {}
22008        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22009        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
22010        editor.highlight_text::<TestHighlight>(
22011            vec![highlight_range.clone()],
22012            HighlightStyle::color(Hsla::green()),
22013            cx,
22014        );
22015        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22016            s.select_ranges(Some(highlight_range))
22017        });
22018    });
22019
22020    let full_text = format!("\n\n{sample_text}");
22021    assert_eq!(
22022        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22023        full_text,
22024    );
22025}
22026
22027#[gpui::test]
22028async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22029    init_test(cx, |_| {});
22030    cx.update(|cx| {
22031        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22032            "keymaps/default-linux.json",
22033            cx,
22034        )
22035        .unwrap();
22036        cx.bind_keys(default_key_bindings);
22037    });
22038
22039    let (editor, cx) = cx.add_window_view(|window, cx| {
22040        let multi_buffer = MultiBuffer::build_multi(
22041            [
22042                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22043                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22044                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22045                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22046            ],
22047            cx,
22048        );
22049        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22050
22051        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22052        // fold all but the second buffer, so that we test navigating between two
22053        // adjacent folded buffers, as well as folded buffers at the start and
22054        // end the multibuffer
22055        editor.fold_buffer(buffer_ids[0], cx);
22056        editor.fold_buffer(buffer_ids[2], cx);
22057        editor.fold_buffer(buffer_ids[3], cx);
22058
22059        editor
22060    });
22061    cx.simulate_resize(size(px(1000.), px(1000.)));
22062
22063    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22064    cx.assert_excerpts_with_selections(indoc! {"
22065        [EXCERPT]
22066        ˇ[FOLDED]
22067        [EXCERPT]
22068        a1
22069        b1
22070        [EXCERPT]
22071        [FOLDED]
22072        [EXCERPT]
22073        [FOLDED]
22074        "
22075    });
22076    cx.simulate_keystroke("down");
22077    cx.assert_excerpts_with_selections(indoc! {"
22078        [EXCERPT]
22079        [FOLDED]
22080        [EXCERPT]
22081        ˇa1
22082        b1
22083        [EXCERPT]
22084        [FOLDED]
22085        [EXCERPT]
22086        [FOLDED]
22087        "
22088    });
22089    cx.simulate_keystroke("down");
22090    cx.assert_excerpts_with_selections(indoc! {"
22091        [EXCERPT]
22092        [FOLDED]
22093        [EXCERPT]
22094        a1
22095        ˇb1
22096        [EXCERPT]
22097        [FOLDED]
22098        [EXCERPT]
22099        [FOLDED]
22100        "
22101    });
22102    cx.simulate_keystroke("down");
22103    cx.assert_excerpts_with_selections(indoc! {"
22104        [EXCERPT]
22105        [FOLDED]
22106        [EXCERPT]
22107        a1
22108        b1
22109        ˇ[EXCERPT]
22110        [FOLDED]
22111        [EXCERPT]
22112        [FOLDED]
22113        "
22114    });
22115    cx.simulate_keystroke("down");
22116    cx.assert_excerpts_with_selections(indoc! {"
22117        [EXCERPT]
22118        [FOLDED]
22119        [EXCERPT]
22120        a1
22121        b1
22122        [EXCERPT]
22123        ˇ[FOLDED]
22124        [EXCERPT]
22125        [FOLDED]
22126        "
22127    });
22128    for _ in 0..5 {
22129        cx.simulate_keystroke("down");
22130        cx.assert_excerpts_with_selections(indoc! {"
22131            [EXCERPT]
22132            [FOLDED]
22133            [EXCERPT]
22134            a1
22135            b1
22136            [EXCERPT]
22137            [FOLDED]
22138            [EXCERPT]
22139            ˇ[FOLDED]
22140            "
22141        });
22142    }
22143
22144    cx.simulate_keystroke("up");
22145    cx.assert_excerpts_with_selections(indoc! {"
22146        [EXCERPT]
22147        [FOLDED]
22148        [EXCERPT]
22149        a1
22150        b1
22151        [EXCERPT]
22152        ˇ[FOLDED]
22153        [EXCERPT]
22154        [FOLDED]
22155        "
22156    });
22157    cx.simulate_keystroke("up");
22158    cx.assert_excerpts_with_selections(indoc! {"
22159        [EXCERPT]
22160        [FOLDED]
22161        [EXCERPT]
22162        a1
22163        b1
22164        ˇ[EXCERPT]
22165        [FOLDED]
22166        [EXCERPT]
22167        [FOLDED]
22168        "
22169    });
22170    cx.simulate_keystroke("up");
22171    cx.assert_excerpts_with_selections(indoc! {"
22172        [EXCERPT]
22173        [FOLDED]
22174        [EXCERPT]
22175        a1
22176        ˇb1
22177        [EXCERPT]
22178        [FOLDED]
22179        [EXCERPT]
22180        [FOLDED]
22181        "
22182    });
22183    cx.simulate_keystroke("up");
22184    cx.assert_excerpts_with_selections(indoc! {"
22185        [EXCERPT]
22186        [FOLDED]
22187        [EXCERPT]
22188        ˇa1
22189        b1
22190        [EXCERPT]
22191        [FOLDED]
22192        [EXCERPT]
22193        [FOLDED]
22194        "
22195    });
22196    for _ in 0..5 {
22197        cx.simulate_keystroke("up");
22198        cx.assert_excerpts_with_selections(indoc! {"
22199            [EXCERPT]
22200            ˇ[FOLDED]
22201            [EXCERPT]
22202            a1
22203            b1
22204            [EXCERPT]
22205            [FOLDED]
22206            [EXCERPT]
22207            [FOLDED]
22208            "
22209        });
22210    }
22211}
22212
22213#[gpui::test]
22214async fn test_edit_prediction_text(cx: &mut TestAppContext) {
22215    init_test(cx, |_| {});
22216
22217    // Simple insertion
22218    assert_highlighted_edits(
22219        "Hello, world!",
22220        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
22221        true,
22222        cx,
22223        |highlighted_edits, cx| {
22224            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
22225            assert_eq!(highlighted_edits.highlights.len(), 1);
22226            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
22227            assert_eq!(
22228                highlighted_edits.highlights[0].1.background_color,
22229                Some(cx.theme().status().created_background)
22230            );
22231        },
22232    )
22233    .await;
22234
22235    // Replacement
22236    assert_highlighted_edits(
22237        "This is a test.",
22238        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
22239        false,
22240        cx,
22241        |highlighted_edits, cx| {
22242            assert_eq!(highlighted_edits.text, "That is a test.");
22243            assert_eq!(highlighted_edits.highlights.len(), 1);
22244            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
22245            assert_eq!(
22246                highlighted_edits.highlights[0].1.background_color,
22247                Some(cx.theme().status().created_background)
22248            );
22249        },
22250    )
22251    .await;
22252
22253    // Multiple edits
22254    assert_highlighted_edits(
22255        "Hello, world!",
22256        vec![
22257            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
22258            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
22259        ],
22260        false,
22261        cx,
22262        |highlighted_edits, cx| {
22263            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
22264            assert_eq!(highlighted_edits.highlights.len(), 2);
22265            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
22266            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
22267            assert_eq!(
22268                highlighted_edits.highlights[0].1.background_color,
22269                Some(cx.theme().status().created_background)
22270            );
22271            assert_eq!(
22272                highlighted_edits.highlights[1].1.background_color,
22273                Some(cx.theme().status().created_background)
22274            );
22275        },
22276    )
22277    .await;
22278
22279    // Multiple lines with edits
22280    assert_highlighted_edits(
22281        "First line\nSecond line\nThird line\nFourth line",
22282        vec![
22283            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
22284            (
22285                Point::new(2, 0)..Point::new(2, 10),
22286                "New third line".to_string(),
22287            ),
22288            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
22289        ],
22290        false,
22291        cx,
22292        |highlighted_edits, cx| {
22293            assert_eq!(
22294                highlighted_edits.text,
22295                "Second modified\nNew third line\nFourth updated line"
22296            );
22297            assert_eq!(highlighted_edits.highlights.len(), 3);
22298            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
22299            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
22300            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
22301            for highlight in &highlighted_edits.highlights {
22302                assert_eq!(
22303                    highlight.1.background_color,
22304                    Some(cx.theme().status().created_background)
22305                );
22306            }
22307        },
22308    )
22309    .await;
22310}
22311
22312#[gpui::test]
22313async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
22314    init_test(cx, |_| {});
22315
22316    // Deletion
22317    assert_highlighted_edits(
22318        "Hello, world!",
22319        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
22320        true,
22321        cx,
22322        |highlighted_edits, cx| {
22323            assert_eq!(highlighted_edits.text, "Hello, world!");
22324            assert_eq!(highlighted_edits.highlights.len(), 1);
22325            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
22326            assert_eq!(
22327                highlighted_edits.highlights[0].1.background_color,
22328                Some(cx.theme().status().deleted_background)
22329            );
22330        },
22331    )
22332    .await;
22333
22334    // Insertion
22335    assert_highlighted_edits(
22336        "Hello, world!",
22337        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
22338        true,
22339        cx,
22340        |highlighted_edits, cx| {
22341            assert_eq!(highlighted_edits.highlights.len(), 1);
22342            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
22343            assert_eq!(
22344                highlighted_edits.highlights[0].1.background_color,
22345                Some(cx.theme().status().created_background)
22346            );
22347        },
22348    )
22349    .await;
22350}
22351
22352async fn assert_highlighted_edits(
22353    text: &str,
22354    edits: Vec<(Range<Point>, String)>,
22355    include_deletions: bool,
22356    cx: &mut TestAppContext,
22357    assertion_fn: impl Fn(HighlightedText, &App),
22358) {
22359    let window = cx.add_window(|window, cx| {
22360        let buffer = MultiBuffer::build_simple(text, cx);
22361        Editor::new(EditorMode::full(), buffer, None, window, cx)
22362    });
22363    let cx = &mut VisualTestContext::from_window(*window, cx);
22364
22365    let (buffer, snapshot) = window
22366        .update(cx, |editor, _window, cx| {
22367            (
22368                editor.buffer().clone(),
22369                editor.buffer().read(cx).snapshot(cx),
22370            )
22371        })
22372        .unwrap();
22373
22374    let edits = edits
22375        .into_iter()
22376        .map(|(range, edit)| {
22377            (
22378                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
22379                edit,
22380            )
22381        })
22382        .collect::<Vec<_>>();
22383
22384    let text_anchor_edits = edits
22385        .clone()
22386        .into_iter()
22387        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
22388        .collect::<Vec<_>>();
22389
22390    let edit_preview = window
22391        .update(cx, |_, _window, cx| {
22392            buffer
22393                .read(cx)
22394                .as_singleton()
22395                .unwrap()
22396                .read(cx)
22397                .preview_edits(text_anchor_edits.into(), cx)
22398        })
22399        .unwrap()
22400        .await;
22401
22402    cx.update(|_window, cx| {
22403        let highlighted_edits = edit_prediction_edit_text(
22404            snapshot.as_singleton().unwrap().2,
22405            &edits,
22406            &edit_preview,
22407            include_deletions,
22408            cx,
22409        );
22410        assertion_fn(highlighted_edits, cx)
22411    });
22412}
22413
22414#[track_caller]
22415fn assert_breakpoint(
22416    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
22417    path: &Arc<Path>,
22418    expected: Vec<(u32, Breakpoint)>,
22419) {
22420    if expected.is_empty() {
22421        assert!(!breakpoints.contains_key(path), "{}", path.display());
22422    } else {
22423        let mut breakpoint = breakpoints
22424            .get(path)
22425            .unwrap()
22426            .iter()
22427            .map(|breakpoint| {
22428                (
22429                    breakpoint.row,
22430                    Breakpoint {
22431                        message: breakpoint.message.clone(),
22432                        state: breakpoint.state,
22433                        condition: breakpoint.condition.clone(),
22434                        hit_condition: breakpoint.hit_condition.clone(),
22435                    },
22436                )
22437            })
22438            .collect::<Vec<_>>();
22439
22440        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
22441
22442        assert_eq!(expected, breakpoint);
22443    }
22444}
22445
22446fn add_log_breakpoint_at_cursor(
22447    editor: &mut Editor,
22448    log_message: &str,
22449    window: &mut Window,
22450    cx: &mut Context<Editor>,
22451) {
22452    let (anchor, bp) = editor
22453        .breakpoints_at_cursors(window, cx)
22454        .first()
22455        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
22456        .unwrap_or_else(|| {
22457            let cursor_position: Point = editor.selections.newest(cx).head();
22458
22459            let breakpoint_position = editor
22460                .snapshot(window, cx)
22461                .display_snapshot
22462                .buffer_snapshot
22463                .anchor_before(Point::new(cursor_position.row, 0));
22464
22465            (breakpoint_position, Breakpoint::new_log(log_message))
22466        });
22467
22468    editor.edit_breakpoint_at_anchor(
22469        anchor,
22470        bp,
22471        BreakpointEditAction::EditLogMessage(log_message.into()),
22472        cx,
22473    );
22474}
22475
22476#[gpui::test]
22477async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
22478    init_test(cx, |_| {});
22479
22480    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22481    let fs = FakeFs::new(cx.executor());
22482    fs.insert_tree(
22483        path!("/a"),
22484        json!({
22485            "main.rs": sample_text,
22486        }),
22487    )
22488    .await;
22489    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22490    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22491    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22492
22493    let fs = FakeFs::new(cx.executor());
22494    fs.insert_tree(
22495        path!("/a"),
22496        json!({
22497            "main.rs": sample_text,
22498        }),
22499    )
22500    .await;
22501    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22502    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22503    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22504    let worktree_id = workspace
22505        .update(cx, |workspace, _window, cx| {
22506            workspace.project().update(cx, |project, cx| {
22507                project.worktrees(cx).next().unwrap().read(cx).id()
22508            })
22509        })
22510        .unwrap();
22511
22512    let buffer = project
22513        .update(cx, |project, cx| {
22514            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22515        })
22516        .await
22517        .unwrap();
22518
22519    let (editor, cx) = cx.add_window_view(|window, cx| {
22520        Editor::new(
22521            EditorMode::full(),
22522            MultiBuffer::build_from_buffer(buffer, cx),
22523            Some(project.clone()),
22524            window,
22525            cx,
22526        )
22527    });
22528
22529    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22530    let abs_path = project.read_with(cx, |project, cx| {
22531        project
22532            .absolute_path(&project_path, cx)
22533            .map(Arc::from)
22534            .unwrap()
22535    });
22536
22537    // assert we can add breakpoint on the first line
22538    editor.update_in(cx, |editor, window, cx| {
22539        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22540        editor.move_to_end(&MoveToEnd, window, cx);
22541        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22542    });
22543
22544    let breakpoints = editor.update(cx, |editor, cx| {
22545        editor
22546            .breakpoint_store()
22547            .as_ref()
22548            .unwrap()
22549            .read(cx)
22550            .all_source_breakpoints(cx)
22551    });
22552
22553    assert_eq!(1, breakpoints.len());
22554    assert_breakpoint(
22555        &breakpoints,
22556        &abs_path,
22557        vec![
22558            (0, Breakpoint::new_standard()),
22559            (3, Breakpoint::new_standard()),
22560        ],
22561    );
22562
22563    editor.update_in(cx, |editor, window, cx| {
22564        editor.move_to_beginning(&MoveToBeginning, window, cx);
22565        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22566    });
22567
22568    let breakpoints = editor.update(cx, |editor, cx| {
22569        editor
22570            .breakpoint_store()
22571            .as_ref()
22572            .unwrap()
22573            .read(cx)
22574            .all_source_breakpoints(cx)
22575    });
22576
22577    assert_eq!(1, breakpoints.len());
22578    assert_breakpoint(
22579        &breakpoints,
22580        &abs_path,
22581        vec![(3, Breakpoint::new_standard())],
22582    );
22583
22584    editor.update_in(cx, |editor, window, cx| {
22585        editor.move_to_end(&MoveToEnd, window, cx);
22586        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22587    });
22588
22589    let breakpoints = editor.update(cx, |editor, cx| {
22590        editor
22591            .breakpoint_store()
22592            .as_ref()
22593            .unwrap()
22594            .read(cx)
22595            .all_source_breakpoints(cx)
22596    });
22597
22598    assert_eq!(0, breakpoints.len());
22599    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22600}
22601
22602#[gpui::test]
22603async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
22604    init_test(cx, |_| {});
22605
22606    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22607
22608    let fs = FakeFs::new(cx.executor());
22609    fs.insert_tree(
22610        path!("/a"),
22611        json!({
22612            "main.rs": sample_text,
22613        }),
22614    )
22615    .await;
22616    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22617    let (workspace, cx) =
22618        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22619
22620    let worktree_id = workspace.update(cx, |workspace, cx| {
22621        workspace.project().update(cx, |project, cx| {
22622            project.worktrees(cx).next().unwrap().read(cx).id()
22623        })
22624    });
22625
22626    let buffer = project
22627        .update(cx, |project, cx| {
22628            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22629        })
22630        .await
22631        .unwrap();
22632
22633    let (editor, cx) = cx.add_window_view(|window, cx| {
22634        Editor::new(
22635            EditorMode::full(),
22636            MultiBuffer::build_from_buffer(buffer, cx),
22637            Some(project.clone()),
22638            window,
22639            cx,
22640        )
22641    });
22642
22643    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22644    let abs_path = project.read_with(cx, |project, cx| {
22645        project
22646            .absolute_path(&project_path, cx)
22647            .map(Arc::from)
22648            .unwrap()
22649    });
22650
22651    editor.update_in(cx, |editor, window, cx| {
22652        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22653    });
22654
22655    let breakpoints = editor.update(cx, |editor, cx| {
22656        editor
22657            .breakpoint_store()
22658            .as_ref()
22659            .unwrap()
22660            .read(cx)
22661            .all_source_breakpoints(cx)
22662    });
22663
22664    assert_breakpoint(
22665        &breakpoints,
22666        &abs_path,
22667        vec![(0, Breakpoint::new_log("hello world"))],
22668    );
22669
22670    // Removing a log message from a log breakpoint should remove it
22671    editor.update_in(cx, |editor, window, cx| {
22672        add_log_breakpoint_at_cursor(editor, "", window, cx);
22673    });
22674
22675    let breakpoints = editor.update(cx, |editor, cx| {
22676        editor
22677            .breakpoint_store()
22678            .as_ref()
22679            .unwrap()
22680            .read(cx)
22681            .all_source_breakpoints(cx)
22682    });
22683
22684    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22685
22686    editor.update_in(cx, |editor, window, cx| {
22687        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22688        editor.move_to_end(&MoveToEnd, window, cx);
22689        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22690        // Not adding a log message to a standard breakpoint shouldn't remove it
22691        add_log_breakpoint_at_cursor(editor, "", window, cx);
22692    });
22693
22694    let breakpoints = editor.update(cx, |editor, cx| {
22695        editor
22696            .breakpoint_store()
22697            .as_ref()
22698            .unwrap()
22699            .read(cx)
22700            .all_source_breakpoints(cx)
22701    });
22702
22703    assert_breakpoint(
22704        &breakpoints,
22705        &abs_path,
22706        vec![
22707            (0, Breakpoint::new_standard()),
22708            (3, Breakpoint::new_standard()),
22709        ],
22710    );
22711
22712    editor.update_in(cx, |editor, window, cx| {
22713        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22714    });
22715
22716    let breakpoints = editor.update(cx, |editor, cx| {
22717        editor
22718            .breakpoint_store()
22719            .as_ref()
22720            .unwrap()
22721            .read(cx)
22722            .all_source_breakpoints(cx)
22723    });
22724
22725    assert_breakpoint(
22726        &breakpoints,
22727        &abs_path,
22728        vec![
22729            (0, Breakpoint::new_standard()),
22730            (3, Breakpoint::new_log("hello world")),
22731        ],
22732    );
22733
22734    editor.update_in(cx, |editor, window, cx| {
22735        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
22736    });
22737
22738    let breakpoints = editor.update(cx, |editor, cx| {
22739        editor
22740            .breakpoint_store()
22741            .as_ref()
22742            .unwrap()
22743            .read(cx)
22744            .all_source_breakpoints(cx)
22745    });
22746
22747    assert_breakpoint(
22748        &breakpoints,
22749        &abs_path,
22750        vec![
22751            (0, Breakpoint::new_standard()),
22752            (3, Breakpoint::new_log("hello Earth!!")),
22753        ],
22754    );
22755}
22756
22757/// This also tests that Editor::breakpoint_at_cursor_head is working properly
22758/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
22759/// or when breakpoints were placed out of order. This tests for a regression too
22760#[gpui::test]
22761async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
22762    init_test(cx, |_| {});
22763
22764    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22765    let fs = FakeFs::new(cx.executor());
22766    fs.insert_tree(
22767        path!("/a"),
22768        json!({
22769            "main.rs": sample_text,
22770        }),
22771    )
22772    .await;
22773    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22774    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22775    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22776
22777    let fs = FakeFs::new(cx.executor());
22778    fs.insert_tree(
22779        path!("/a"),
22780        json!({
22781            "main.rs": sample_text,
22782        }),
22783    )
22784    .await;
22785    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22786    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22787    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22788    let worktree_id = workspace
22789        .update(cx, |workspace, _window, cx| {
22790            workspace.project().update(cx, |project, cx| {
22791                project.worktrees(cx).next().unwrap().read(cx).id()
22792            })
22793        })
22794        .unwrap();
22795
22796    let buffer = project
22797        .update(cx, |project, cx| {
22798            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22799        })
22800        .await
22801        .unwrap();
22802
22803    let (editor, cx) = cx.add_window_view(|window, cx| {
22804        Editor::new(
22805            EditorMode::full(),
22806            MultiBuffer::build_from_buffer(buffer, cx),
22807            Some(project.clone()),
22808            window,
22809            cx,
22810        )
22811    });
22812
22813    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22814    let abs_path = project.read_with(cx, |project, cx| {
22815        project
22816            .absolute_path(&project_path, cx)
22817            .map(Arc::from)
22818            .unwrap()
22819    });
22820
22821    // assert we can add breakpoint on the first line
22822    editor.update_in(cx, |editor, window, cx| {
22823        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22824        editor.move_to_end(&MoveToEnd, window, cx);
22825        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22826        editor.move_up(&MoveUp, window, cx);
22827        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22828    });
22829
22830    let breakpoints = editor.update(cx, |editor, cx| {
22831        editor
22832            .breakpoint_store()
22833            .as_ref()
22834            .unwrap()
22835            .read(cx)
22836            .all_source_breakpoints(cx)
22837    });
22838
22839    assert_eq!(1, breakpoints.len());
22840    assert_breakpoint(
22841        &breakpoints,
22842        &abs_path,
22843        vec![
22844            (0, Breakpoint::new_standard()),
22845            (2, Breakpoint::new_standard()),
22846            (3, Breakpoint::new_standard()),
22847        ],
22848    );
22849
22850    editor.update_in(cx, |editor, window, cx| {
22851        editor.move_to_beginning(&MoveToBeginning, window, cx);
22852        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22853        editor.move_to_end(&MoveToEnd, window, cx);
22854        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22855        // Disabling a breakpoint that doesn't exist should do nothing
22856        editor.move_up(&MoveUp, window, cx);
22857        editor.move_up(&MoveUp, window, cx);
22858        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22859    });
22860
22861    let breakpoints = editor.update(cx, |editor, cx| {
22862        editor
22863            .breakpoint_store()
22864            .as_ref()
22865            .unwrap()
22866            .read(cx)
22867            .all_source_breakpoints(cx)
22868    });
22869
22870    let disable_breakpoint = {
22871        let mut bp = Breakpoint::new_standard();
22872        bp.state = BreakpointState::Disabled;
22873        bp
22874    };
22875
22876    assert_eq!(1, breakpoints.len());
22877    assert_breakpoint(
22878        &breakpoints,
22879        &abs_path,
22880        vec![
22881            (0, disable_breakpoint.clone()),
22882            (2, Breakpoint::new_standard()),
22883            (3, disable_breakpoint.clone()),
22884        ],
22885    );
22886
22887    editor.update_in(cx, |editor, window, cx| {
22888        editor.move_to_beginning(&MoveToBeginning, window, cx);
22889        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22890        editor.move_to_end(&MoveToEnd, window, cx);
22891        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22892        editor.move_up(&MoveUp, window, cx);
22893        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22894    });
22895
22896    let breakpoints = editor.update(cx, |editor, cx| {
22897        editor
22898            .breakpoint_store()
22899            .as_ref()
22900            .unwrap()
22901            .read(cx)
22902            .all_source_breakpoints(cx)
22903    });
22904
22905    assert_eq!(1, breakpoints.len());
22906    assert_breakpoint(
22907        &breakpoints,
22908        &abs_path,
22909        vec![
22910            (0, Breakpoint::new_standard()),
22911            (2, disable_breakpoint),
22912            (3, Breakpoint::new_standard()),
22913        ],
22914    );
22915}
22916
22917#[gpui::test]
22918async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
22919    init_test(cx, |_| {});
22920    let capabilities = lsp::ServerCapabilities {
22921        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
22922            prepare_provider: Some(true),
22923            work_done_progress_options: Default::default(),
22924        })),
22925        ..Default::default()
22926    };
22927    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
22928
22929    cx.set_state(indoc! {"
22930        struct Fˇoo {}
22931    "});
22932
22933    cx.update_editor(|editor, _, cx| {
22934        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
22935        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
22936        editor.highlight_background::<DocumentHighlightRead>(
22937            &[highlight_range],
22938            |theme| theme.colors().editor_document_highlight_read_background,
22939            cx,
22940        );
22941    });
22942
22943    let mut prepare_rename_handler = cx
22944        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
22945            move |_, _, _| async move {
22946                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
22947                    start: lsp::Position {
22948                        line: 0,
22949                        character: 7,
22950                    },
22951                    end: lsp::Position {
22952                        line: 0,
22953                        character: 10,
22954                    },
22955                })))
22956            },
22957        );
22958    let prepare_rename_task = cx
22959        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
22960        .expect("Prepare rename was not started");
22961    prepare_rename_handler.next().await.unwrap();
22962    prepare_rename_task.await.expect("Prepare rename failed");
22963
22964    let mut rename_handler =
22965        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
22966            let edit = lsp::TextEdit {
22967                range: lsp::Range {
22968                    start: lsp::Position {
22969                        line: 0,
22970                        character: 7,
22971                    },
22972                    end: lsp::Position {
22973                        line: 0,
22974                        character: 10,
22975                    },
22976                },
22977                new_text: "FooRenamed".to_string(),
22978            };
22979            Ok(Some(lsp::WorkspaceEdit::new(
22980                // Specify the same edit twice
22981                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
22982            )))
22983        });
22984    let rename_task = cx
22985        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
22986        .expect("Confirm rename was not started");
22987    rename_handler.next().await.unwrap();
22988    rename_task.await.expect("Confirm rename failed");
22989    cx.run_until_parked();
22990
22991    // Despite two edits, only one is actually applied as those are identical
22992    cx.assert_editor_state(indoc! {"
22993        struct FooRenamedˇ {}
22994    "});
22995}
22996
22997#[gpui::test]
22998async fn test_rename_without_prepare(cx: &mut TestAppContext) {
22999    init_test(cx, |_| {});
23000    // These capabilities indicate that the server does not support prepare rename.
23001    let capabilities = lsp::ServerCapabilities {
23002        rename_provider: Some(lsp::OneOf::Left(true)),
23003        ..Default::default()
23004    };
23005    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23006
23007    cx.set_state(indoc! {"
23008        struct Fˇoo {}
23009    "});
23010
23011    cx.update_editor(|editor, _window, cx| {
23012        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23013        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23014        editor.highlight_background::<DocumentHighlightRead>(
23015            &[highlight_range],
23016            |theme| theme.colors().editor_document_highlight_read_background,
23017            cx,
23018        );
23019    });
23020
23021    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23022        .expect("Prepare rename was not started")
23023        .await
23024        .expect("Prepare rename failed");
23025
23026    let mut rename_handler =
23027        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23028            let edit = lsp::TextEdit {
23029                range: lsp::Range {
23030                    start: lsp::Position {
23031                        line: 0,
23032                        character: 7,
23033                    },
23034                    end: lsp::Position {
23035                        line: 0,
23036                        character: 10,
23037                    },
23038                },
23039                new_text: "FooRenamed".to_string(),
23040            };
23041            Ok(Some(lsp::WorkspaceEdit::new(
23042                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23043            )))
23044        });
23045    let rename_task = cx
23046        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23047        .expect("Confirm rename was not started");
23048    rename_handler.next().await.unwrap();
23049    rename_task.await.expect("Confirm rename failed");
23050    cx.run_until_parked();
23051
23052    // Correct range is renamed, as `surrounding_word` is used to find it.
23053    cx.assert_editor_state(indoc! {"
23054        struct FooRenamedˇ {}
23055    "});
23056}
23057
23058#[gpui::test]
23059async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23060    init_test(cx, |_| {});
23061    let mut cx = EditorTestContext::new(cx).await;
23062
23063    let language = Arc::new(
23064        Language::new(
23065            LanguageConfig::default(),
23066            Some(tree_sitter_html::LANGUAGE.into()),
23067        )
23068        .with_brackets_query(
23069            r#"
23070            ("<" @open "/>" @close)
23071            ("</" @open ">" @close)
23072            ("<" @open ">" @close)
23073            ("\"" @open "\"" @close)
23074            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23075        "#,
23076        )
23077        .unwrap(),
23078    );
23079    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23080
23081    cx.set_state(indoc! {"
23082        <span>ˇ</span>
23083    "});
23084    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23085    cx.assert_editor_state(indoc! {"
23086        <span>
23087        ˇ
23088        </span>
23089    "});
23090
23091    cx.set_state(indoc! {"
23092        <span><span></span>ˇ</span>
23093    "});
23094    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23095    cx.assert_editor_state(indoc! {"
23096        <span><span></span>
23097        ˇ</span>
23098    "});
23099
23100    cx.set_state(indoc! {"
23101        <span>ˇ
23102        </span>
23103    "});
23104    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23105    cx.assert_editor_state(indoc! {"
23106        <span>
23107        ˇ
23108        </span>
23109    "});
23110}
23111
23112#[gpui::test(iterations = 10)]
23113async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23114    init_test(cx, |_| {});
23115
23116    let fs = FakeFs::new(cx.executor());
23117    fs.insert_tree(
23118        path!("/dir"),
23119        json!({
23120            "a.ts": "a",
23121        }),
23122    )
23123    .await;
23124
23125    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23126    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23127    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23128
23129    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23130    language_registry.add(Arc::new(Language::new(
23131        LanguageConfig {
23132            name: "TypeScript".into(),
23133            matcher: LanguageMatcher {
23134                path_suffixes: vec!["ts".to_string()],
23135                ..Default::default()
23136            },
23137            ..Default::default()
23138        },
23139        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
23140    )));
23141    let mut fake_language_servers = language_registry.register_fake_lsp(
23142        "TypeScript",
23143        FakeLspAdapter {
23144            capabilities: lsp::ServerCapabilities {
23145                code_lens_provider: Some(lsp::CodeLensOptions {
23146                    resolve_provider: Some(true),
23147                }),
23148                execute_command_provider: Some(lsp::ExecuteCommandOptions {
23149                    commands: vec!["_the/command".to_string()],
23150                    ..lsp::ExecuteCommandOptions::default()
23151                }),
23152                ..lsp::ServerCapabilities::default()
23153            },
23154            ..FakeLspAdapter::default()
23155        },
23156    );
23157
23158    let editor = workspace
23159        .update(cx, |workspace, window, cx| {
23160            workspace.open_abs_path(
23161                PathBuf::from(path!("/dir/a.ts")),
23162                OpenOptions::default(),
23163                window,
23164                cx,
23165            )
23166        })
23167        .unwrap()
23168        .await
23169        .unwrap()
23170        .downcast::<Editor>()
23171        .unwrap();
23172    cx.executor().run_until_parked();
23173
23174    let fake_server = fake_language_servers.next().await.unwrap();
23175
23176    let buffer = editor.update(cx, |editor, cx| {
23177        editor
23178            .buffer()
23179            .read(cx)
23180            .as_singleton()
23181            .expect("have opened a single file by path")
23182    });
23183
23184    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
23185    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
23186    drop(buffer_snapshot);
23187    let actions = cx
23188        .update_window(*workspace, |_, window, cx| {
23189            project.code_actions(&buffer, anchor..anchor, window, cx)
23190        })
23191        .unwrap();
23192
23193    fake_server
23194        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23195            Ok(Some(vec![
23196                lsp::CodeLens {
23197                    range: lsp::Range::default(),
23198                    command: Some(lsp::Command {
23199                        title: "Code lens command".to_owned(),
23200                        command: "_the/command".to_owned(),
23201                        arguments: None,
23202                    }),
23203                    data: None,
23204                },
23205                lsp::CodeLens {
23206                    range: lsp::Range::default(),
23207                    command: Some(lsp::Command {
23208                        title: "Command not in capabilities".to_owned(),
23209                        command: "not in capabilities".to_owned(),
23210                        arguments: None,
23211                    }),
23212                    data: None,
23213                },
23214                lsp::CodeLens {
23215                    range: lsp::Range {
23216                        start: lsp::Position {
23217                            line: 1,
23218                            character: 1,
23219                        },
23220                        end: lsp::Position {
23221                            line: 1,
23222                            character: 1,
23223                        },
23224                    },
23225                    command: Some(lsp::Command {
23226                        title: "Command not in range".to_owned(),
23227                        command: "_the/command".to_owned(),
23228                        arguments: None,
23229                    }),
23230                    data: None,
23231                },
23232            ]))
23233        })
23234        .next()
23235        .await;
23236
23237    let actions = actions.await.unwrap();
23238    assert_eq!(
23239        actions.len(),
23240        1,
23241        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
23242    );
23243    let action = actions[0].clone();
23244    let apply = project.update(cx, |project, cx| {
23245        project.apply_code_action(buffer.clone(), action, true, cx)
23246    });
23247
23248    // Resolving the code action does not populate its edits. In absence of
23249    // edits, we must execute the given command.
23250    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
23251        |mut lens, _| async move {
23252            let lens_command = lens.command.as_mut().expect("should have a command");
23253            assert_eq!(lens_command.title, "Code lens command");
23254            lens_command.arguments = Some(vec![json!("the-argument")]);
23255            Ok(lens)
23256        },
23257    );
23258
23259    // While executing the command, the language server sends the editor
23260    // a `workspaceEdit` request.
23261    fake_server
23262        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
23263            let fake = fake_server.clone();
23264            move |params, _| {
23265                assert_eq!(params.command, "_the/command");
23266                let fake = fake.clone();
23267                async move {
23268                    fake.server
23269                        .request::<lsp::request::ApplyWorkspaceEdit>(
23270                            lsp::ApplyWorkspaceEditParams {
23271                                label: None,
23272                                edit: lsp::WorkspaceEdit {
23273                                    changes: Some(
23274                                        [(
23275                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
23276                                            vec![lsp::TextEdit {
23277                                                range: lsp::Range::new(
23278                                                    lsp::Position::new(0, 0),
23279                                                    lsp::Position::new(0, 0),
23280                                                ),
23281                                                new_text: "X".into(),
23282                                            }],
23283                                        )]
23284                                        .into_iter()
23285                                        .collect(),
23286                                    ),
23287                                    ..lsp::WorkspaceEdit::default()
23288                                },
23289                            },
23290                        )
23291                        .await
23292                        .into_response()
23293                        .unwrap();
23294                    Ok(Some(json!(null)))
23295                }
23296            }
23297        })
23298        .next()
23299        .await;
23300
23301    // Applying the code lens command returns a project transaction containing the edits
23302    // sent by the language server in its `workspaceEdit` request.
23303    let transaction = apply.await.unwrap();
23304    assert!(transaction.0.contains_key(&buffer));
23305    buffer.update(cx, |buffer, cx| {
23306        assert_eq!(buffer.text(), "Xa");
23307        buffer.undo(cx);
23308        assert_eq!(buffer.text(), "a");
23309    });
23310
23311    let actions_after_edits = cx
23312        .update_window(*workspace, |_, window, cx| {
23313            project.code_actions(&buffer, anchor..anchor, window, cx)
23314        })
23315        .unwrap()
23316        .await
23317        .unwrap();
23318    assert_eq!(
23319        actions, actions_after_edits,
23320        "For the same selection, same code lens actions should be returned"
23321    );
23322
23323    let _responses =
23324        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23325            panic!("No more code lens requests are expected");
23326        });
23327    editor.update_in(cx, |editor, window, cx| {
23328        editor.select_all(&SelectAll, window, cx);
23329    });
23330    cx.executor().run_until_parked();
23331    let new_actions = cx
23332        .update_window(*workspace, |_, window, cx| {
23333            project.code_actions(&buffer, anchor..anchor, window, cx)
23334        })
23335        .unwrap()
23336        .await
23337        .unwrap();
23338    assert_eq!(
23339        actions, new_actions,
23340        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
23341    );
23342}
23343
23344#[gpui::test]
23345async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
23346    init_test(cx, |_| {});
23347
23348    let fs = FakeFs::new(cx.executor());
23349    let main_text = r#"fn main() {
23350println!("1");
23351println!("2");
23352println!("3");
23353println!("4");
23354println!("5");
23355}"#;
23356    let lib_text = "mod foo {}";
23357    fs.insert_tree(
23358        path!("/a"),
23359        json!({
23360            "lib.rs": lib_text,
23361            "main.rs": main_text,
23362        }),
23363    )
23364    .await;
23365
23366    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23367    let (workspace, cx) =
23368        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23369    let worktree_id = workspace.update(cx, |workspace, cx| {
23370        workspace.project().update(cx, |project, cx| {
23371            project.worktrees(cx).next().unwrap().read(cx).id()
23372        })
23373    });
23374
23375    let expected_ranges = vec![
23376        Point::new(0, 0)..Point::new(0, 0),
23377        Point::new(1, 0)..Point::new(1, 1),
23378        Point::new(2, 0)..Point::new(2, 2),
23379        Point::new(3, 0)..Point::new(3, 3),
23380    ];
23381
23382    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23383    let editor_1 = workspace
23384        .update_in(cx, |workspace, window, cx| {
23385            workspace.open_path(
23386                (worktree_id, rel_path("main.rs")),
23387                Some(pane_1.downgrade()),
23388                true,
23389                window,
23390                cx,
23391            )
23392        })
23393        .unwrap()
23394        .await
23395        .downcast::<Editor>()
23396        .unwrap();
23397    pane_1.update(cx, |pane, cx| {
23398        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23399        open_editor.update(cx, |editor, cx| {
23400            assert_eq!(
23401                editor.display_text(cx),
23402                main_text,
23403                "Original main.rs text on initial open",
23404            );
23405            assert_eq!(
23406                editor
23407                    .selections
23408                    .all::<Point>(cx)
23409                    .into_iter()
23410                    .map(|s| s.range())
23411                    .collect::<Vec<_>>(),
23412                vec![Point::zero()..Point::zero()],
23413                "Default selections on initial open",
23414            );
23415        })
23416    });
23417    editor_1.update_in(cx, |editor, window, cx| {
23418        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23419            s.select_ranges(expected_ranges.clone());
23420        });
23421    });
23422
23423    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
23424        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
23425    });
23426    let editor_2 = workspace
23427        .update_in(cx, |workspace, window, cx| {
23428            workspace.open_path(
23429                (worktree_id, rel_path("main.rs")),
23430                Some(pane_2.downgrade()),
23431                true,
23432                window,
23433                cx,
23434            )
23435        })
23436        .unwrap()
23437        .await
23438        .downcast::<Editor>()
23439        .unwrap();
23440    pane_2.update(cx, |pane, cx| {
23441        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23442        open_editor.update(cx, |editor, cx| {
23443            assert_eq!(
23444                editor.display_text(cx),
23445                main_text,
23446                "Original main.rs text on initial open in another panel",
23447            );
23448            assert_eq!(
23449                editor
23450                    .selections
23451                    .all::<Point>(cx)
23452                    .into_iter()
23453                    .map(|s| s.range())
23454                    .collect::<Vec<_>>(),
23455                vec![Point::zero()..Point::zero()],
23456                "Default selections on initial open in another panel",
23457            );
23458        })
23459    });
23460
23461    editor_2.update_in(cx, |editor, window, cx| {
23462        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23463    });
23464
23465    let _other_editor_1 = workspace
23466        .update_in(cx, |workspace, window, cx| {
23467            workspace.open_path(
23468                (worktree_id, rel_path("lib.rs")),
23469                Some(pane_1.downgrade()),
23470                true,
23471                window,
23472                cx,
23473            )
23474        })
23475        .unwrap()
23476        .await
23477        .downcast::<Editor>()
23478        .unwrap();
23479    pane_1
23480        .update_in(cx, |pane, window, cx| {
23481            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23482        })
23483        .await
23484        .unwrap();
23485    drop(editor_1);
23486    pane_1.update(cx, |pane, cx| {
23487        pane.active_item()
23488            .unwrap()
23489            .downcast::<Editor>()
23490            .unwrap()
23491            .update(cx, |editor, cx| {
23492                assert_eq!(
23493                    editor.display_text(cx),
23494                    lib_text,
23495                    "Other file should be open and active",
23496                );
23497            });
23498        assert_eq!(pane.items().count(), 1, "No other editors should be open");
23499    });
23500
23501    let _other_editor_2 = workspace
23502        .update_in(cx, |workspace, window, cx| {
23503            workspace.open_path(
23504                (worktree_id, rel_path("lib.rs")),
23505                Some(pane_2.downgrade()),
23506                true,
23507                window,
23508                cx,
23509            )
23510        })
23511        .unwrap()
23512        .await
23513        .downcast::<Editor>()
23514        .unwrap();
23515    pane_2
23516        .update_in(cx, |pane, window, cx| {
23517            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23518        })
23519        .await
23520        .unwrap();
23521    drop(editor_2);
23522    pane_2.update(cx, |pane, cx| {
23523        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23524        open_editor.update(cx, |editor, cx| {
23525            assert_eq!(
23526                editor.display_text(cx),
23527                lib_text,
23528                "Other file should be open and active in another panel too",
23529            );
23530        });
23531        assert_eq!(
23532            pane.items().count(),
23533            1,
23534            "No other editors should be open in another pane",
23535        );
23536    });
23537
23538    let _editor_1_reopened = workspace
23539        .update_in(cx, |workspace, window, cx| {
23540            workspace.open_path(
23541                (worktree_id, rel_path("main.rs")),
23542                Some(pane_1.downgrade()),
23543                true,
23544                window,
23545                cx,
23546            )
23547        })
23548        .unwrap()
23549        .await
23550        .downcast::<Editor>()
23551        .unwrap();
23552    let _editor_2_reopened = workspace
23553        .update_in(cx, |workspace, window, cx| {
23554            workspace.open_path(
23555                (worktree_id, rel_path("main.rs")),
23556                Some(pane_2.downgrade()),
23557                true,
23558                window,
23559                cx,
23560            )
23561        })
23562        .unwrap()
23563        .await
23564        .downcast::<Editor>()
23565        .unwrap();
23566    pane_1.update(cx, |pane, cx| {
23567        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23568        open_editor.update(cx, |editor, cx| {
23569            assert_eq!(
23570                editor.display_text(cx),
23571                main_text,
23572                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
23573            );
23574            assert_eq!(
23575                editor
23576                    .selections
23577                    .all::<Point>(cx)
23578                    .into_iter()
23579                    .map(|s| s.range())
23580                    .collect::<Vec<_>>(),
23581                expected_ranges,
23582                "Previous editor in the 1st panel had selections and should get them restored on reopen",
23583            );
23584        })
23585    });
23586    pane_2.update(cx, |pane, cx| {
23587        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23588        open_editor.update(cx, |editor, cx| {
23589            assert_eq!(
23590                editor.display_text(cx),
23591                r#"fn main() {
23592⋯rintln!("1");
23593⋯intln!("2");
23594⋯ntln!("3");
23595println!("4");
23596println!("5");
23597}"#,
23598                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
23599            );
23600            assert_eq!(
23601                editor
23602                    .selections
23603                    .all::<Point>(cx)
23604                    .into_iter()
23605                    .map(|s| s.range())
23606                    .collect::<Vec<_>>(),
23607                vec![Point::zero()..Point::zero()],
23608                "Previous editor in the 2nd pane had no selections changed hence should restore none",
23609            );
23610        })
23611    });
23612}
23613
23614#[gpui::test]
23615async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
23616    init_test(cx, |_| {});
23617
23618    let fs = FakeFs::new(cx.executor());
23619    let main_text = r#"fn main() {
23620println!("1");
23621println!("2");
23622println!("3");
23623println!("4");
23624println!("5");
23625}"#;
23626    let lib_text = "mod foo {}";
23627    fs.insert_tree(
23628        path!("/a"),
23629        json!({
23630            "lib.rs": lib_text,
23631            "main.rs": main_text,
23632        }),
23633    )
23634    .await;
23635
23636    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23637    let (workspace, cx) =
23638        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23639    let worktree_id = workspace.update(cx, |workspace, cx| {
23640        workspace.project().update(cx, |project, cx| {
23641            project.worktrees(cx).next().unwrap().read(cx).id()
23642        })
23643    });
23644
23645    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23646    let editor = workspace
23647        .update_in(cx, |workspace, window, cx| {
23648            workspace.open_path(
23649                (worktree_id, rel_path("main.rs")),
23650                Some(pane.downgrade()),
23651                true,
23652                window,
23653                cx,
23654            )
23655        })
23656        .unwrap()
23657        .await
23658        .downcast::<Editor>()
23659        .unwrap();
23660    pane.update(cx, |pane, cx| {
23661        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23662        open_editor.update(cx, |editor, cx| {
23663            assert_eq!(
23664                editor.display_text(cx),
23665                main_text,
23666                "Original main.rs text on initial open",
23667            );
23668        })
23669    });
23670    editor.update_in(cx, |editor, window, cx| {
23671        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
23672    });
23673
23674    cx.update_global(|store: &mut SettingsStore, cx| {
23675        store.update_user_settings(cx, |s| {
23676            s.workspace.restore_on_file_reopen = Some(false);
23677        });
23678    });
23679    editor.update_in(cx, |editor, window, cx| {
23680        editor.fold_ranges(
23681            vec![
23682                Point::new(1, 0)..Point::new(1, 1),
23683                Point::new(2, 0)..Point::new(2, 2),
23684                Point::new(3, 0)..Point::new(3, 3),
23685            ],
23686            false,
23687            window,
23688            cx,
23689        );
23690    });
23691    pane.update_in(cx, |pane, window, cx| {
23692        pane.close_all_items(&CloseAllItems::default(), window, cx)
23693    })
23694    .await
23695    .unwrap();
23696    pane.update(cx, |pane, _| {
23697        assert!(pane.active_item().is_none());
23698    });
23699    cx.update_global(|store: &mut SettingsStore, cx| {
23700        store.update_user_settings(cx, |s| {
23701            s.workspace.restore_on_file_reopen = Some(true);
23702        });
23703    });
23704
23705    let _editor_reopened = workspace
23706        .update_in(cx, |workspace, window, cx| {
23707            workspace.open_path(
23708                (worktree_id, rel_path("main.rs")),
23709                Some(pane.downgrade()),
23710                true,
23711                window,
23712                cx,
23713            )
23714        })
23715        .unwrap()
23716        .await
23717        .downcast::<Editor>()
23718        .unwrap();
23719    pane.update(cx, |pane, cx| {
23720        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23721        open_editor.update(cx, |editor, cx| {
23722            assert_eq!(
23723                editor.display_text(cx),
23724                main_text,
23725                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
23726            );
23727        })
23728    });
23729}
23730
23731#[gpui::test]
23732async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
23733    struct EmptyModalView {
23734        focus_handle: gpui::FocusHandle,
23735    }
23736    impl EventEmitter<DismissEvent> for EmptyModalView {}
23737    impl Render for EmptyModalView {
23738        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
23739            div()
23740        }
23741    }
23742    impl Focusable for EmptyModalView {
23743        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
23744            self.focus_handle.clone()
23745        }
23746    }
23747    impl workspace::ModalView for EmptyModalView {}
23748    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
23749        EmptyModalView {
23750            focus_handle: cx.focus_handle(),
23751        }
23752    }
23753
23754    init_test(cx, |_| {});
23755
23756    let fs = FakeFs::new(cx.executor());
23757    let project = Project::test(fs, [], cx).await;
23758    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23759    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
23760    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23761    let editor = cx.new_window_entity(|window, cx| {
23762        Editor::new(
23763            EditorMode::full(),
23764            buffer,
23765            Some(project.clone()),
23766            window,
23767            cx,
23768        )
23769    });
23770    workspace
23771        .update(cx, |workspace, window, cx| {
23772            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
23773        })
23774        .unwrap();
23775    editor.update_in(cx, |editor, window, cx| {
23776        editor.open_context_menu(&OpenContextMenu, window, cx);
23777        assert!(editor.mouse_context_menu.is_some());
23778    });
23779    workspace
23780        .update(cx, |workspace, window, cx| {
23781            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
23782        })
23783        .unwrap();
23784    cx.read(|cx| {
23785        assert!(editor.read(cx).mouse_context_menu.is_none());
23786    });
23787}
23788
23789fn set_linked_edit_ranges(
23790    opening: (Point, Point),
23791    closing: (Point, Point),
23792    editor: &mut Editor,
23793    cx: &mut Context<Editor>,
23794) {
23795    let Some((buffer, _)) = editor
23796        .buffer
23797        .read(cx)
23798        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
23799    else {
23800        panic!("Failed to get buffer for selection position");
23801    };
23802    let buffer = buffer.read(cx);
23803    let buffer_id = buffer.remote_id();
23804    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
23805    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
23806    let mut linked_ranges = HashMap::default();
23807    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
23808    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
23809}
23810
23811#[gpui::test]
23812async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
23813    init_test(cx, |_| {});
23814
23815    let fs = FakeFs::new(cx.executor());
23816    fs.insert_file(path!("/file.html"), Default::default())
23817        .await;
23818
23819    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
23820
23821    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23822    let html_language = Arc::new(Language::new(
23823        LanguageConfig {
23824            name: "HTML".into(),
23825            matcher: LanguageMatcher {
23826                path_suffixes: vec!["html".to_string()],
23827                ..LanguageMatcher::default()
23828            },
23829            brackets: BracketPairConfig {
23830                pairs: vec![BracketPair {
23831                    start: "<".into(),
23832                    end: ">".into(),
23833                    close: true,
23834                    ..Default::default()
23835                }],
23836                ..Default::default()
23837            },
23838            ..Default::default()
23839        },
23840        Some(tree_sitter_html::LANGUAGE.into()),
23841    ));
23842    language_registry.add(html_language);
23843    let mut fake_servers = language_registry.register_fake_lsp(
23844        "HTML",
23845        FakeLspAdapter {
23846            capabilities: lsp::ServerCapabilities {
23847                completion_provider: Some(lsp::CompletionOptions {
23848                    resolve_provider: Some(true),
23849                    ..Default::default()
23850                }),
23851                ..Default::default()
23852            },
23853            ..Default::default()
23854        },
23855    );
23856
23857    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23858    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23859
23860    let worktree_id = workspace
23861        .update(cx, |workspace, _window, cx| {
23862            workspace.project().update(cx, |project, cx| {
23863                project.worktrees(cx).next().unwrap().read(cx).id()
23864            })
23865        })
23866        .unwrap();
23867    project
23868        .update(cx, |project, cx| {
23869            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
23870        })
23871        .await
23872        .unwrap();
23873    let editor = workspace
23874        .update(cx, |workspace, window, cx| {
23875            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
23876        })
23877        .unwrap()
23878        .await
23879        .unwrap()
23880        .downcast::<Editor>()
23881        .unwrap();
23882
23883    let fake_server = fake_servers.next().await.unwrap();
23884    editor.update_in(cx, |editor, window, cx| {
23885        editor.set_text("<ad></ad>", window, cx);
23886        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23887            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
23888        });
23889        set_linked_edit_ranges(
23890            (Point::new(0, 1), Point::new(0, 3)),
23891            (Point::new(0, 6), Point::new(0, 8)),
23892            editor,
23893            cx,
23894        );
23895    });
23896    let mut completion_handle =
23897        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
23898            Ok(Some(lsp::CompletionResponse::Array(vec![
23899                lsp::CompletionItem {
23900                    label: "head".to_string(),
23901                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23902                        lsp::InsertReplaceEdit {
23903                            new_text: "head".to_string(),
23904                            insert: lsp::Range::new(
23905                                lsp::Position::new(0, 1),
23906                                lsp::Position::new(0, 3),
23907                            ),
23908                            replace: lsp::Range::new(
23909                                lsp::Position::new(0, 1),
23910                                lsp::Position::new(0, 3),
23911                            ),
23912                        },
23913                    )),
23914                    ..Default::default()
23915                },
23916            ])))
23917        });
23918    editor.update_in(cx, |editor, window, cx| {
23919        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
23920    });
23921    cx.run_until_parked();
23922    completion_handle.next().await.unwrap();
23923    editor.update(cx, |editor, _| {
23924        assert!(
23925            editor.context_menu_visible(),
23926            "Completion menu should be visible"
23927        );
23928    });
23929    editor.update_in(cx, |editor, window, cx| {
23930        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
23931    });
23932    cx.executor().run_until_parked();
23933    editor.update(cx, |editor, cx| {
23934        assert_eq!(editor.text(cx), "<head></head>");
23935    });
23936}
23937
23938#[gpui::test]
23939async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
23940    init_test(cx, |_| {});
23941
23942    let mut cx = EditorTestContext::new(cx).await;
23943    let language = Arc::new(Language::new(
23944        LanguageConfig {
23945            name: "TSX".into(),
23946            matcher: LanguageMatcher {
23947                path_suffixes: vec!["tsx".to_string()],
23948                ..LanguageMatcher::default()
23949            },
23950            brackets: BracketPairConfig {
23951                pairs: vec![BracketPair {
23952                    start: "<".into(),
23953                    end: ">".into(),
23954                    close: true,
23955                    ..Default::default()
23956                }],
23957                ..Default::default()
23958            },
23959            linked_edit_characters: HashSet::from_iter(['.']),
23960            ..Default::default()
23961        },
23962        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
23963    ));
23964    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23965
23966    // Test typing > does not extend linked pair
23967    cx.set_state("<divˇ<div></div>");
23968    cx.update_editor(|editor, _, cx| {
23969        set_linked_edit_ranges(
23970            (Point::new(0, 1), Point::new(0, 4)),
23971            (Point::new(0, 11), Point::new(0, 14)),
23972            editor,
23973            cx,
23974        );
23975    });
23976    cx.update_editor(|editor, window, cx| {
23977        editor.handle_input(">", window, cx);
23978    });
23979    cx.assert_editor_state("<div>ˇ<div></div>");
23980
23981    // Test typing . do extend linked pair
23982    cx.set_state("<Animatedˇ></Animated>");
23983    cx.update_editor(|editor, _, cx| {
23984        set_linked_edit_ranges(
23985            (Point::new(0, 1), Point::new(0, 9)),
23986            (Point::new(0, 12), Point::new(0, 20)),
23987            editor,
23988            cx,
23989        );
23990    });
23991    cx.update_editor(|editor, window, cx| {
23992        editor.handle_input(".", window, cx);
23993    });
23994    cx.assert_editor_state("<Animated.ˇ></Animated.>");
23995    cx.update_editor(|editor, _, cx| {
23996        set_linked_edit_ranges(
23997            (Point::new(0, 1), Point::new(0, 10)),
23998            (Point::new(0, 13), Point::new(0, 21)),
23999            editor,
24000            cx,
24001        );
24002    });
24003    cx.update_editor(|editor, window, cx| {
24004        editor.handle_input("V", window, cx);
24005    });
24006    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
24007}
24008
24009#[gpui::test]
24010async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
24011    init_test(cx, |_| {});
24012
24013    let fs = FakeFs::new(cx.executor());
24014    fs.insert_tree(
24015        path!("/root"),
24016        json!({
24017            "a": {
24018                "main.rs": "fn main() {}",
24019            },
24020            "foo": {
24021                "bar": {
24022                    "external_file.rs": "pub mod external {}",
24023                }
24024            }
24025        }),
24026    )
24027    .await;
24028
24029    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
24030    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24031    language_registry.add(rust_lang());
24032    let _fake_servers = language_registry.register_fake_lsp(
24033        "Rust",
24034        FakeLspAdapter {
24035            ..FakeLspAdapter::default()
24036        },
24037    );
24038    let (workspace, cx) =
24039        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24040    let worktree_id = workspace.update(cx, |workspace, cx| {
24041        workspace.project().update(cx, |project, cx| {
24042            project.worktrees(cx).next().unwrap().read(cx).id()
24043        })
24044    });
24045
24046    let assert_language_servers_count =
24047        |expected: usize, context: &str, cx: &mut VisualTestContext| {
24048            project.update(cx, |project, cx| {
24049                let current = project
24050                    .lsp_store()
24051                    .read(cx)
24052                    .as_local()
24053                    .unwrap()
24054                    .language_servers
24055                    .len();
24056                assert_eq!(expected, current, "{context}");
24057            });
24058        };
24059
24060    assert_language_servers_count(
24061        0,
24062        "No servers should be running before any file is open",
24063        cx,
24064    );
24065    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24066    let main_editor = workspace
24067        .update_in(cx, |workspace, window, cx| {
24068            workspace.open_path(
24069                (worktree_id, rel_path("main.rs")),
24070                Some(pane.downgrade()),
24071                true,
24072                window,
24073                cx,
24074            )
24075        })
24076        .unwrap()
24077        .await
24078        .downcast::<Editor>()
24079        .unwrap();
24080    pane.update(cx, |pane, cx| {
24081        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24082        open_editor.update(cx, |editor, cx| {
24083            assert_eq!(
24084                editor.display_text(cx),
24085                "fn main() {}",
24086                "Original main.rs text on initial open",
24087            );
24088        });
24089        assert_eq!(open_editor, main_editor);
24090    });
24091    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24092
24093    let external_editor = workspace
24094        .update_in(cx, |workspace, window, cx| {
24095            workspace.open_abs_path(
24096                PathBuf::from("/root/foo/bar/external_file.rs"),
24097                OpenOptions::default(),
24098                window,
24099                cx,
24100            )
24101        })
24102        .await
24103        .expect("opening external file")
24104        .downcast::<Editor>()
24105        .expect("downcasted external file's open element to editor");
24106    pane.update(cx, |pane, cx| {
24107        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24108        open_editor.update(cx, |editor, cx| {
24109            assert_eq!(
24110                editor.display_text(cx),
24111                "pub mod external {}",
24112                "External file is open now",
24113            );
24114        });
24115        assert_eq!(open_editor, external_editor);
24116    });
24117    assert_language_servers_count(
24118        1,
24119        "Second, external, *.rs file should join the existing server",
24120        cx,
24121    );
24122
24123    pane.update_in(cx, |pane, window, cx| {
24124        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24125    })
24126    .await
24127    .unwrap();
24128    pane.update_in(cx, |pane, window, cx| {
24129        pane.navigate_backward(&Default::default(), window, cx);
24130    });
24131    cx.run_until_parked();
24132    pane.update(cx, |pane, cx| {
24133        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24134        open_editor.update(cx, |editor, cx| {
24135            assert_eq!(
24136                editor.display_text(cx),
24137                "pub mod external {}",
24138                "External file is open now",
24139            );
24140        });
24141    });
24142    assert_language_servers_count(
24143        1,
24144        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
24145        cx,
24146    );
24147
24148    cx.update(|_, cx| {
24149        workspace::reload(cx);
24150    });
24151    assert_language_servers_count(
24152        1,
24153        "After reloading the worktree with local and external files opened, only one project should be started",
24154        cx,
24155    );
24156}
24157
24158#[gpui::test]
24159async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
24160    init_test(cx, |_| {});
24161
24162    let mut cx = EditorTestContext::new(cx).await;
24163    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24164    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24165
24166    // test cursor move to start of each line on tab
24167    // for `if`, `elif`, `else`, `while`, `with` and `for`
24168    cx.set_state(indoc! {"
24169        def main():
24170        ˇ    for item in items:
24171        ˇ        while item.active:
24172        ˇ            if item.value > 10:
24173        ˇ                continue
24174        ˇ            elif item.value < 0:
24175        ˇ                break
24176        ˇ            else:
24177        ˇ                with item.context() as ctx:
24178        ˇ                    yield count
24179        ˇ        else:
24180        ˇ            log('while else')
24181        ˇ    else:
24182        ˇ        log('for else')
24183    "});
24184    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24185    cx.assert_editor_state(indoc! {"
24186        def main():
24187            ˇfor item in items:
24188                ˇwhile item.active:
24189                    ˇif item.value > 10:
24190                        ˇcontinue
24191                    ˇelif item.value < 0:
24192                        ˇbreak
24193                    ˇelse:
24194                        ˇwith item.context() as ctx:
24195                            ˇyield count
24196                ˇelse:
24197                    ˇlog('while else')
24198            ˇelse:
24199                ˇlog('for else')
24200    "});
24201    // test relative indent is preserved when tab
24202    // for `if`, `elif`, `else`, `while`, `with` and `for`
24203    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24204    cx.assert_editor_state(indoc! {"
24205        def main():
24206                ˇfor item in items:
24207                    ˇwhile item.active:
24208                        ˇif item.value > 10:
24209                            ˇcontinue
24210                        ˇelif item.value < 0:
24211                            ˇbreak
24212                        ˇelse:
24213                            ˇwith item.context() as ctx:
24214                                ˇyield count
24215                    ˇelse:
24216                        ˇlog('while else')
24217                ˇelse:
24218                    ˇlog('for else')
24219    "});
24220
24221    // test cursor move to start of each line on tab
24222    // for `try`, `except`, `else`, `finally`, `match` and `def`
24223    cx.set_state(indoc! {"
24224        def main():
24225        ˇ    try:
24226        ˇ        fetch()
24227        ˇ    except ValueError:
24228        ˇ        handle_error()
24229        ˇ    else:
24230        ˇ        match value:
24231        ˇ            case _:
24232        ˇ    finally:
24233        ˇ        def status():
24234        ˇ            return 0
24235    "});
24236    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24237    cx.assert_editor_state(indoc! {"
24238        def main():
24239            ˇtry:
24240                ˇfetch()
24241            ˇexcept ValueError:
24242                ˇhandle_error()
24243            ˇelse:
24244                ˇmatch value:
24245                    ˇcase _:
24246            ˇfinally:
24247                ˇdef status():
24248                    ˇreturn 0
24249    "});
24250    // test relative indent is preserved when tab
24251    // for `try`, `except`, `else`, `finally`, `match` and `def`
24252    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24253    cx.assert_editor_state(indoc! {"
24254        def main():
24255                ˇtry:
24256                    ˇfetch()
24257                ˇexcept ValueError:
24258                    ˇhandle_error()
24259                ˇelse:
24260                    ˇmatch value:
24261                        ˇcase _:
24262                ˇfinally:
24263                    ˇdef status():
24264                        ˇreturn 0
24265    "});
24266}
24267
24268#[gpui::test]
24269async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
24270    init_test(cx, |_| {});
24271
24272    let mut cx = EditorTestContext::new(cx).await;
24273    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24274    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24275
24276    // test `else` auto outdents when typed inside `if` block
24277    cx.set_state(indoc! {"
24278        def main():
24279            if i == 2:
24280                return
24281                ˇ
24282    "});
24283    cx.update_editor(|editor, window, cx| {
24284        editor.handle_input("else:", window, cx);
24285    });
24286    cx.assert_editor_state(indoc! {"
24287        def main():
24288            if i == 2:
24289                return
24290            else:ˇ
24291    "});
24292
24293    // test `except` auto outdents when typed inside `try` block
24294    cx.set_state(indoc! {"
24295        def main():
24296            try:
24297                i = 2
24298                ˇ
24299    "});
24300    cx.update_editor(|editor, window, cx| {
24301        editor.handle_input("except:", window, cx);
24302    });
24303    cx.assert_editor_state(indoc! {"
24304        def main():
24305            try:
24306                i = 2
24307            except:ˇ
24308    "});
24309
24310    // test `else` auto outdents when typed inside `except` block
24311    cx.set_state(indoc! {"
24312        def main():
24313            try:
24314                i = 2
24315            except:
24316                j = 2
24317                ˇ
24318    "});
24319    cx.update_editor(|editor, window, cx| {
24320        editor.handle_input("else:", window, cx);
24321    });
24322    cx.assert_editor_state(indoc! {"
24323        def main():
24324            try:
24325                i = 2
24326            except:
24327                j = 2
24328            else:ˇ
24329    "});
24330
24331    // test `finally` auto outdents when typed inside `else` block
24332    cx.set_state(indoc! {"
24333        def main():
24334            try:
24335                i = 2
24336            except:
24337                j = 2
24338            else:
24339                k = 2
24340                ˇ
24341    "});
24342    cx.update_editor(|editor, window, cx| {
24343        editor.handle_input("finally:", window, cx);
24344    });
24345    cx.assert_editor_state(indoc! {"
24346        def main():
24347            try:
24348                i = 2
24349            except:
24350                j = 2
24351            else:
24352                k = 2
24353            finally:ˇ
24354    "});
24355
24356    // test `else` does not outdents when typed inside `except` block right after for block
24357    cx.set_state(indoc! {"
24358        def main():
24359            try:
24360                i = 2
24361            except:
24362                for i in range(n):
24363                    pass
24364                ˇ
24365    "});
24366    cx.update_editor(|editor, window, cx| {
24367        editor.handle_input("else:", window, cx);
24368    });
24369    cx.assert_editor_state(indoc! {"
24370        def main():
24371            try:
24372                i = 2
24373            except:
24374                for i in range(n):
24375                    pass
24376                else:ˇ
24377    "});
24378
24379    // test `finally` auto outdents when typed inside `else` block right after for block
24380    cx.set_state(indoc! {"
24381        def main():
24382            try:
24383                i = 2
24384            except:
24385                j = 2
24386            else:
24387                for i in range(n):
24388                    pass
24389                ˇ
24390    "});
24391    cx.update_editor(|editor, window, cx| {
24392        editor.handle_input("finally:", window, cx);
24393    });
24394    cx.assert_editor_state(indoc! {"
24395        def main():
24396            try:
24397                i = 2
24398            except:
24399                j = 2
24400            else:
24401                for i in range(n):
24402                    pass
24403            finally:ˇ
24404    "});
24405
24406    // test `except` outdents to inner "try" block
24407    cx.set_state(indoc! {"
24408        def main():
24409            try:
24410                i = 2
24411                if i == 2:
24412                    try:
24413                        i = 3
24414                        ˇ
24415    "});
24416    cx.update_editor(|editor, window, cx| {
24417        editor.handle_input("except:", window, cx);
24418    });
24419    cx.assert_editor_state(indoc! {"
24420        def main():
24421            try:
24422                i = 2
24423                if i == 2:
24424                    try:
24425                        i = 3
24426                    except:ˇ
24427    "});
24428
24429    // test `except` outdents to outer "try" block
24430    cx.set_state(indoc! {"
24431        def main():
24432            try:
24433                i = 2
24434                if i == 2:
24435                    try:
24436                        i = 3
24437                ˇ
24438    "});
24439    cx.update_editor(|editor, window, cx| {
24440        editor.handle_input("except:", window, cx);
24441    });
24442    cx.assert_editor_state(indoc! {"
24443        def main():
24444            try:
24445                i = 2
24446                if i == 2:
24447                    try:
24448                        i = 3
24449            except:ˇ
24450    "});
24451
24452    // test `else` stays at correct indent when typed after `for` block
24453    cx.set_state(indoc! {"
24454        def main():
24455            for i in range(10):
24456                if i == 3:
24457                    break
24458            ˇ
24459    "});
24460    cx.update_editor(|editor, window, cx| {
24461        editor.handle_input("else:", window, cx);
24462    });
24463    cx.assert_editor_state(indoc! {"
24464        def main():
24465            for i in range(10):
24466                if i == 3:
24467                    break
24468            else:ˇ
24469    "});
24470
24471    // test does not outdent on typing after line with square brackets
24472    cx.set_state(indoc! {"
24473        def f() -> list[str]:
24474            ˇ
24475    "});
24476    cx.update_editor(|editor, window, cx| {
24477        editor.handle_input("a", window, cx);
24478    });
24479    cx.assert_editor_state(indoc! {"
24480        def f() -> list[str]:
2448124482    "});
24483
24484    // test does not outdent on typing : after case keyword
24485    cx.set_state(indoc! {"
24486        match 1:
24487            caseˇ
24488    "});
24489    cx.update_editor(|editor, window, cx| {
24490        editor.handle_input(":", window, cx);
24491    });
24492    cx.assert_editor_state(indoc! {"
24493        match 1:
24494            case:ˇ
24495    "});
24496}
24497
24498#[gpui::test]
24499async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
24500    init_test(cx, |_| {});
24501    update_test_language_settings(cx, |settings| {
24502        settings.defaults.extend_comment_on_newline = Some(false);
24503    });
24504    let mut cx = EditorTestContext::new(cx).await;
24505    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24506    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24507
24508    // test correct indent after newline on comment
24509    cx.set_state(indoc! {"
24510        # COMMENT:ˇ
24511    "});
24512    cx.update_editor(|editor, window, cx| {
24513        editor.newline(&Newline, window, cx);
24514    });
24515    cx.assert_editor_state(indoc! {"
24516        # COMMENT:
24517        ˇ
24518    "});
24519
24520    // test correct indent after newline in brackets
24521    cx.set_state(indoc! {"
24522        {ˇ}
24523    "});
24524    cx.update_editor(|editor, window, cx| {
24525        editor.newline(&Newline, window, cx);
24526    });
24527    cx.run_until_parked();
24528    cx.assert_editor_state(indoc! {"
24529        {
24530            ˇ
24531        }
24532    "});
24533
24534    cx.set_state(indoc! {"
24535        (ˇ)
24536    "});
24537    cx.update_editor(|editor, window, cx| {
24538        editor.newline(&Newline, window, cx);
24539    });
24540    cx.run_until_parked();
24541    cx.assert_editor_state(indoc! {"
24542        (
24543            ˇ
24544        )
24545    "});
24546
24547    // do not indent after empty lists or dictionaries
24548    cx.set_state(indoc! {"
24549        a = []ˇ
24550    "});
24551    cx.update_editor(|editor, window, cx| {
24552        editor.newline(&Newline, window, cx);
24553    });
24554    cx.run_until_parked();
24555    cx.assert_editor_state(indoc! {"
24556        a = []
24557        ˇ
24558    "});
24559}
24560
24561#[gpui::test]
24562async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
24563    init_test(cx, |_| {});
24564
24565    let mut cx = EditorTestContext::new(cx).await;
24566    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24567    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24568
24569    // test cursor move to start of each line on tab
24570    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
24571    cx.set_state(indoc! {"
24572        function main() {
24573        ˇ    for item in $items; do
24574        ˇ        while [ -n \"$item\" ]; do
24575        ˇ            if [ \"$value\" -gt 10 ]; then
24576        ˇ                continue
24577        ˇ            elif [ \"$value\" -lt 0 ]; then
24578        ˇ                break
24579        ˇ            else
24580        ˇ                echo \"$item\"
24581        ˇ            fi
24582        ˇ        done
24583        ˇ    done
24584        ˇ}
24585    "});
24586    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24587    cx.assert_editor_state(indoc! {"
24588        function main() {
24589            ˇfor item in $items; do
24590                ˇwhile [ -n \"$item\" ]; do
24591                    ˇif [ \"$value\" -gt 10 ]; then
24592                        ˇcontinue
24593                    ˇelif [ \"$value\" -lt 0 ]; then
24594                        ˇbreak
24595                    ˇelse
24596                        ˇecho \"$item\"
24597                    ˇfi
24598                ˇdone
24599            ˇdone
24600        ˇ}
24601    "});
24602    // test relative indent is preserved when tab
24603    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24604    cx.assert_editor_state(indoc! {"
24605        function main() {
24606                ˇfor item in $items; do
24607                    ˇwhile [ -n \"$item\" ]; do
24608                        ˇif [ \"$value\" -gt 10 ]; then
24609                            ˇcontinue
24610                        ˇelif [ \"$value\" -lt 0 ]; then
24611                            ˇbreak
24612                        ˇelse
24613                            ˇecho \"$item\"
24614                        ˇfi
24615                    ˇdone
24616                ˇdone
24617            ˇ}
24618    "});
24619
24620    // test cursor move to start of each line on tab
24621    // for `case` statement with patterns
24622    cx.set_state(indoc! {"
24623        function handle() {
24624        ˇ    case \"$1\" in
24625        ˇ        start)
24626        ˇ            echo \"a\"
24627        ˇ            ;;
24628        ˇ        stop)
24629        ˇ            echo \"b\"
24630        ˇ            ;;
24631        ˇ        *)
24632        ˇ            echo \"c\"
24633        ˇ            ;;
24634        ˇ    esac
24635        ˇ}
24636    "});
24637    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24638    cx.assert_editor_state(indoc! {"
24639        function handle() {
24640            ˇcase \"$1\" in
24641                ˇstart)
24642                    ˇecho \"a\"
24643                    ˇ;;
24644                ˇstop)
24645                    ˇecho \"b\"
24646                    ˇ;;
24647                ˇ*)
24648                    ˇecho \"c\"
24649                    ˇ;;
24650            ˇesac
24651        ˇ}
24652    "});
24653}
24654
24655#[gpui::test]
24656async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
24657    init_test(cx, |_| {});
24658
24659    let mut cx = EditorTestContext::new(cx).await;
24660    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24661    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24662
24663    // test indents on comment insert
24664    cx.set_state(indoc! {"
24665        function main() {
24666        ˇ    for item in $items; do
24667        ˇ        while [ -n \"$item\" ]; do
24668        ˇ            if [ \"$value\" -gt 10 ]; then
24669        ˇ                continue
24670        ˇ            elif [ \"$value\" -lt 0 ]; then
24671        ˇ                break
24672        ˇ            else
24673        ˇ                echo \"$item\"
24674        ˇ            fi
24675        ˇ        done
24676        ˇ    done
24677        ˇ}
24678    "});
24679    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
24680    cx.assert_editor_state(indoc! {"
24681        function main() {
24682        #ˇ    for item in $items; do
24683        #ˇ        while [ -n \"$item\" ]; do
24684        #ˇ            if [ \"$value\" -gt 10 ]; then
24685        #ˇ                continue
24686        #ˇ            elif [ \"$value\" -lt 0 ]; then
24687        #ˇ                break
24688        #ˇ            else
24689        #ˇ                echo \"$item\"
24690        #ˇ            fi
24691        #ˇ        done
24692        #ˇ    done
24693        #ˇ}
24694    "});
24695}
24696
24697#[gpui::test]
24698async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
24699    init_test(cx, |_| {});
24700
24701    let mut cx = EditorTestContext::new(cx).await;
24702    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24703    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24704
24705    // test `else` auto outdents when typed inside `if` block
24706    cx.set_state(indoc! {"
24707        if [ \"$1\" = \"test\" ]; then
24708            echo \"foo bar\"
24709            ˇ
24710    "});
24711    cx.update_editor(|editor, window, cx| {
24712        editor.handle_input("else", window, cx);
24713    });
24714    cx.assert_editor_state(indoc! {"
24715        if [ \"$1\" = \"test\" ]; then
24716            echo \"foo bar\"
24717        elseˇ
24718    "});
24719
24720    // test `elif` auto outdents when typed inside `if` block
24721    cx.set_state(indoc! {"
24722        if [ \"$1\" = \"test\" ]; then
24723            echo \"foo bar\"
24724            ˇ
24725    "});
24726    cx.update_editor(|editor, window, cx| {
24727        editor.handle_input("elif", window, cx);
24728    });
24729    cx.assert_editor_state(indoc! {"
24730        if [ \"$1\" = \"test\" ]; then
24731            echo \"foo bar\"
24732        elifˇ
24733    "});
24734
24735    // test `fi` auto outdents when typed inside `else` block
24736    cx.set_state(indoc! {"
24737        if [ \"$1\" = \"test\" ]; then
24738            echo \"foo bar\"
24739        else
24740            echo \"bar baz\"
24741            ˇ
24742    "});
24743    cx.update_editor(|editor, window, cx| {
24744        editor.handle_input("fi", window, cx);
24745    });
24746    cx.assert_editor_state(indoc! {"
24747        if [ \"$1\" = \"test\" ]; then
24748            echo \"foo bar\"
24749        else
24750            echo \"bar baz\"
24751        fiˇ
24752    "});
24753
24754    // test `done` auto outdents when typed inside `while` block
24755    cx.set_state(indoc! {"
24756        while read line; do
24757            echo \"$line\"
24758            ˇ
24759    "});
24760    cx.update_editor(|editor, window, cx| {
24761        editor.handle_input("done", window, cx);
24762    });
24763    cx.assert_editor_state(indoc! {"
24764        while read line; do
24765            echo \"$line\"
24766        doneˇ
24767    "});
24768
24769    // test `done` auto outdents when typed inside `for` block
24770    cx.set_state(indoc! {"
24771        for file in *.txt; do
24772            cat \"$file\"
24773            ˇ
24774    "});
24775    cx.update_editor(|editor, window, cx| {
24776        editor.handle_input("done", window, cx);
24777    });
24778    cx.assert_editor_state(indoc! {"
24779        for file in *.txt; do
24780            cat \"$file\"
24781        doneˇ
24782    "});
24783
24784    // test `esac` auto outdents when typed inside `case` block
24785    cx.set_state(indoc! {"
24786        case \"$1\" in
24787            start)
24788                echo \"foo bar\"
24789                ;;
24790            stop)
24791                echo \"bar baz\"
24792                ;;
24793            ˇ
24794    "});
24795    cx.update_editor(|editor, window, cx| {
24796        editor.handle_input("esac", window, cx);
24797    });
24798    cx.assert_editor_state(indoc! {"
24799        case \"$1\" in
24800            start)
24801                echo \"foo bar\"
24802                ;;
24803            stop)
24804                echo \"bar baz\"
24805                ;;
24806        esacˇ
24807    "});
24808
24809    // test `*)` auto outdents when typed inside `case` block
24810    cx.set_state(indoc! {"
24811        case \"$1\" in
24812            start)
24813                echo \"foo bar\"
24814                ;;
24815                ˇ
24816    "});
24817    cx.update_editor(|editor, window, cx| {
24818        editor.handle_input("*)", window, cx);
24819    });
24820    cx.assert_editor_state(indoc! {"
24821        case \"$1\" in
24822            start)
24823                echo \"foo bar\"
24824                ;;
24825            *)ˇ
24826    "});
24827
24828    // test `fi` outdents to correct level with nested if blocks
24829    cx.set_state(indoc! {"
24830        if [ \"$1\" = \"test\" ]; then
24831            echo \"outer if\"
24832            if [ \"$2\" = \"debug\" ]; then
24833                echo \"inner if\"
24834                ˇ
24835    "});
24836    cx.update_editor(|editor, window, cx| {
24837        editor.handle_input("fi", window, cx);
24838    });
24839    cx.assert_editor_state(indoc! {"
24840        if [ \"$1\" = \"test\" ]; then
24841            echo \"outer if\"
24842            if [ \"$2\" = \"debug\" ]; then
24843                echo \"inner if\"
24844            fiˇ
24845    "});
24846}
24847
24848#[gpui::test]
24849async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
24850    init_test(cx, |_| {});
24851    update_test_language_settings(cx, |settings| {
24852        settings.defaults.extend_comment_on_newline = Some(false);
24853    });
24854    let mut cx = EditorTestContext::new(cx).await;
24855    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24856    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24857
24858    // test correct indent after newline on comment
24859    cx.set_state(indoc! {"
24860        # COMMENT:ˇ
24861    "});
24862    cx.update_editor(|editor, window, cx| {
24863        editor.newline(&Newline, window, cx);
24864    });
24865    cx.assert_editor_state(indoc! {"
24866        # COMMENT:
24867        ˇ
24868    "});
24869
24870    // test correct indent after newline after `then`
24871    cx.set_state(indoc! {"
24872
24873        if [ \"$1\" = \"test\" ]; thenˇ
24874    "});
24875    cx.update_editor(|editor, window, cx| {
24876        editor.newline(&Newline, window, cx);
24877    });
24878    cx.run_until_parked();
24879    cx.assert_editor_state(indoc! {"
24880
24881        if [ \"$1\" = \"test\" ]; then
24882            ˇ
24883    "});
24884
24885    // test correct indent after newline after `else`
24886    cx.set_state(indoc! {"
24887        if [ \"$1\" = \"test\" ]; then
24888        elseˇ
24889    "});
24890    cx.update_editor(|editor, window, cx| {
24891        editor.newline(&Newline, window, cx);
24892    });
24893    cx.run_until_parked();
24894    cx.assert_editor_state(indoc! {"
24895        if [ \"$1\" = \"test\" ]; then
24896        else
24897            ˇ
24898    "});
24899
24900    // test correct indent after newline after `elif`
24901    cx.set_state(indoc! {"
24902        if [ \"$1\" = \"test\" ]; then
24903        elifˇ
24904    "});
24905    cx.update_editor(|editor, window, cx| {
24906        editor.newline(&Newline, window, cx);
24907    });
24908    cx.run_until_parked();
24909    cx.assert_editor_state(indoc! {"
24910        if [ \"$1\" = \"test\" ]; then
24911        elif
24912            ˇ
24913    "});
24914
24915    // test correct indent after newline after `do`
24916    cx.set_state(indoc! {"
24917        for file in *.txt; doˇ
24918    "});
24919    cx.update_editor(|editor, window, cx| {
24920        editor.newline(&Newline, window, cx);
24921    });
24922    cx.run_until_parked();
24923    cx.assert_editor_state(indoc! {"
24924        for file in *.txt; do
24925            ˇ
24926    "});
24927
24928    // test correct indent after newline after case pattern
24929    cx.set_state(indoc! {"
24930        case \"$1\" in
24931            start)ˇ
24932    "});
24933    cx.update_editor(|editor, window, cx| {
24934        editor.newline(&Newline, window, cx);
24935    });
24936    cx.run_until_parked();
24937    cx.assert_editor_state(indoc! {"
24938        case \"$1\" in
24939            start)
24940                ˇ
24941    "});
24942
24943    // test correct indent after newline after case pattern
24944    cx.set_state(indoc! {"
24945        case \"$1\" in
24946            start)
24947                ;;
24948            *)ˇ
24949    "});
24950    cx.update_editor(|editor, window, cx| {
24951        editor.newline(&Newline, window, cx);
24952    });
24953    cx.run_until_parked();
24954    cx.assert_editor_state(indoc! {"
24955        case \"$1\" in
24956            start)
24957                ;;
24958            *)
24959                ˇ
24960    "});
24961
24962    // test correct indent after newline after function opening brace
24963    cx.set_state(indoc! {"
24964        function test() {ˇ}
24965    "});
24966    cx.update_editor(|editor, window, cx| {
24967        editor.newline(&Newline, window, cx);
24968    });
24969    cx.run_until_parked();
24970    cx.assert_editor_state(indoc! {"
24971        function test() {
24972            ˇ
24973        }
24974    "});
24975
24976    // test no extra indent after semicolon on same line
24977    cx.set_state(indoc! {"
24978        echo \"test\"24979    "});
24980    cx.update_editor(|editor, window, cx| {
24981        editor.newline(&Newline, window, cx);
24982    });
24983    cx.run_until_parked();
24984    cx.assert_editor_state(indoc! {"
24985        echo \"test\";
24986        ˇ
24987    "});
24988}
24989
24990fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
24991    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
24992    point..point
24993}
24994
24995#[track_caller]
24996fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
24997    let (text, ranges) = marked_text_ranges(marked_text, true);
24998    assert_eq!(editor.text(cx), text);
24999    assert_eq!(
25000        editor.selections.ranges(cx),
25001        ranges,
25002        "Assert selections are {}",
25003        marked_text
25004    );
25005}
25006
25007pub fn handle_signature_help_request(
25008    cx: &mut EditorLspTestContext,
25009    mocked_response: lsp::SignatureHelp,
25010) -> impl Future<Output = ()> + use<> {
25011    let mut request =
25012        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
25013            let mocked_response = mocked_response.clone();
25014            async move { Ok(Some(mocked_response)) }
25015        });
25016
25017    async move {
25018        request.next().await;
25019    }
25020}
25021
25022#[track_caller]
25023pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
25024    cx.update_editor(|editor, _, _| {
25025        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
25026            let entries = menu.entries.borrow();
25027            let entries = entries
25028                .iter()
25029                .map(|entry| entry.string.as_str())
25030                .collect::<Vec<_>>();
25031            assert_eq!(entries, expected);
25032        } else {
25033            panic!("Expected completions menu");
25034        }
25035    });
25036}
25037
25038/// Handle completion request passing a marked string specifying where the completion
25039/// should be triggered from using '|' character, what range should be replaced, and what completions
25040/// should be returned using '<' and '>' to delimit the range.
25041///
25042/// Also see `handle_completion_request_with_insert_and_replace`.
25043#[track_caller]
25044pub fn handle_completion_request(
25045    marked_string: &str,
25046    completions: Vec<&'static str>,
25047    is_incomplete: bool,
25048    counter: Arc<AtomicUsize>,
25049    cx: &mut EditorLspTestContext,
25050) -> impl Future<Output = ()> {
25051    let complete_from_marker: TextRangeMarker = '|'.into();
25052    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25053    let (_, mut marked_ranges) = marked_text_ranges_by(
25054        marked_string,
25055        vec![complete_from_marker.clone(), replace_range_marker.clone()],
25056    );
25057
25058    let complete_from_position =
25059        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25060    let replace_range =
25061        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25062
25063    let mut request =
25064        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25065            let completions = completions.clone();
25066            counter.fetch_add(1, atomic::Ordering::Release);
25067            async move {
25068                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25069                assert_eq!(
25070                    params.text_document_position.position,
25071                    complete_from_position
25072                );
25073                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
25074                    is_incomplete,
25075                    item_defaults: None,
25076                    items: completions
25077                        .iter()
25078                        .map(|completion_text| lsp::CompletionItem {
25079                            label: completion_text.to_string(),
25080                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
25081                                range: replace_range,
25082                                new_text: completion_text.to_string(),
25083                            })),
25084                            ..Default::default()
25085                        })
25086                        .collect(),
25087                })))
25088            }
25089        });
25090
25091    async move {
25092        request.next().await;
25093    }
25094}
25095
25096/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
25097/// given instead, which also contains an `insert` range.
25098///
25099/// This function uses markers to define ranges:
25100/// - `|` marks the cursor position
25101/// - `<>` marks the replace range
25102/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
25103pub fn handle_completion_request_with_insert_and_replace(
25104    cx: &mut EditorLspTestContext,
25105    marked_string: &str,
25106    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
25107    counter: Arc<AtomicUsize>,
25108) -> impl Future<Output = ()> {
25109    let complete_from_marker: TextRangeMarker = '|'.into();
25110    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25111    let insert_range_marker: TextRangeMarker = ('{', '}').into();
25112
25113    let (_, mut marked_ranges) = marked_text_ranges_by(
25114        marked_string,
25115        vec![
25116            complete_from_marker.clone(),
25117            replace_range_marker.clone(),
25118            insert_range_marker.clone(),
25119        ],
25120    );
25121
25122    let complete_from_position =
25123        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25124    let replace_range =
25125        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25126
25127    let insert_range = match marked_ranges.remove(&insert_range_marker) {
25128        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
25129        _ => lsp::Range {
25130            start: replace_range.start,
25131            end: complete_from_position,
25132        },
25133    };
25134
25135    let mut request =
25136        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25137            let completions = completions.clone();
25138            counter.fetch_add(1, atomic::Ordering::Release);
25139            async move {
25140                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25141                assert_eq!(
25142                    params.text_document_position.position, complete_from_position,
25143                    "marker `|` position doesn't match",
25144                );
25145                Ok(Some(lsp::CompletionResponse::Array(
25146                    completions
25147                        .iter()
25148                        .map(|(label, new_text)| lsp::CompletionItem {
25149                            label: label.to_string(),
25150                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25151                                lsp::InsertReplaceEdit {
25152                                    insert: insert_range,
25153                                    replace: replace_range,
25154                                    new_text: new_text.to_string(),
25155                                },
25156                            )),
25157                            ..Default::default()
25158                        })
25159                        .collect(),
25160                )))
25161            }
25162        });
25163
25164    async move {
25165        request.next().await;
25166    }
25167}
25168
25169fn handle_resolve_completion_request(
25170    cx: &mut EditorLspTestContext,
25171    edits: Option<Vec<(&'static str, &'static str)>>,
25172) -> impl Future<Output = ()> {
25173    let edits = edits.map(|edits| {
25174        edits
25175            .iter()
25176            .map(|(marked_string, new_text)| {
25177                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
25178                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
25179                lsp::TextEdit::new(replace_range, new_text.to_string())
25180            })
25181            .collect::<Vec<_>>()
25182    });
25183
25184    let mut request =
25185        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
25186            let edits = edits.clone();
25187            async move {
25188                Ok(lsp::CompletionItem {
25189                    additional_text_edits: edits,
25190                    ..Default::default()
25191                })
25192            }
25193        });
25194
25195    async move {
25196        request.next().await;
25197    }
25198}
25199
25200pub(crate) fn update_test_language_settings(
25201    cx: &mut TestAppContext,
25202    f: impl Fn(&mut AllLanguageSettingsContent),
25203) {
25204    cx.update(|cx| {
25205        SettingsStore::update_global(cx, |store, cx| {
25206            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
25207        });
25208    });
25209}
25210
25211pub(crate) fn update_test_project_settings(
25212    cx: &mut TestAppContext,
25213    f: impl Fn(&mut ProjectSettingsContent),
25214) {
25215    cx.update(|cx| {
25216        SettingsStore::update_global(cx, |store, cx| {
25217            store.update_user_settings(cx, |settings| f(&mut settings.project));
25218        });
25219    });
25220}
25221
25222pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
25223    cx.update(|cx| {
25224        assets::Assets.load_test_fonts(cx);
25225        let store = SettingsStore::test(cx);
25226        cx.set_global(store);
25227        theme::init(theme::LoadThemes::JustBase, cx);
25228        release_channel::init(SemanticVersion::default(), cx);
25229        client::init_settings(cx);
25230        language::init(cx);
25231        Project::init_settings(cx);
25232        workspace::init_settings(cx);
25233        crate::init(cx);
25234    });
25235    zlog::init_test();
25236    update_test_language_settings(cx, f);
25237}
25238
25239#[track_caller]
25240fn assert_hunk_revert(
25241    not_reverted_text_with_selections: &str,
25242    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
25243    expected_reverted_text_with_selections: &str,
25244    base_text: &str,
25245    cx: &mut EditorLspTestContext,
25246) {
25247    cx.set_state(not_reverted_text_with_selections);
25248    cx.set_head_text(base_text);
25249    cx.executor().run_until_parked();
25250
25251    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
25252        let snapshot = editor.snapshot(window, cx);
25253        let reverted_hunk_statuses = snapshot
25254            .buffer_snapshot
25255            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
25256            .map(|hunk| hunk.status().kind)
25257            .collect::<Vec<_>>();
25258
25259        editor.git_restore(&Default::default(), window, cx);
25260        reverted_hunk_statuses
25261    });
25262    cx.executor().run_until_parked();
25263    cx.assert_editor_state(expected_reverted_text_with_selections);
25264    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
25265}
25266
25267#[gpui::test(iterations = 10)]
25268async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
25269    init_test(cx, |_| {});
25270
25271    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
25272    let counter = diagnostic_requests.clone();
25273
25274    let fs = FakeFs::new(cx.executor());
25275    fs.insert_tree(
25276        path!("/a"),
25277        json!({
25278            "first.rs": "fn main() { let a = 5; }",
25279            "second.rs": "// Test file",
25280        }),
25281    )
25282    .await;
25283
25284    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25285    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25286    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25287
25288    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25289    language_registry.add(rust_lang());
25290    let mut fake_servers = language_registry.register_fake_lsp(
25291        "Rust",
25292        FakeLspAdapter {
25293            capabilities: lsp::ServerCapabilities {
25294                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
25295                    lsp::DiagnosticOptions {
25296                        identifier: None,
25297                        inter_file_dependencies: true,
25298                        workspace_diagnostics: true,
25299                        work_done_progress_options: Default::default(),
25300                    },
25301                )),
25302                ..Default::default()
25303            },
25304            ..Default::default()
25305        },
25306    );
25307
25308    let editor = workspace
25309        .update(cx, |workspace, window, cx| {
25310            workspace.open_abs_path(
25311                PathBuf::from(path!("/a/first.rs")),
25312                OpenOptions::default(),
25313                window,
25314                cx,
25315            )
25316        })
25317        .unwrap()
25318        .await
25319        .unwrap()
25320        .downcast::<Editor>()
25321        .unwrap();
25322    let fake_server = fake_servers.next().await.unwrap();
25323    let server_id = fake_server.server.server_id();
25324    let mut first_request = fake_server
25325        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
25326            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
25327            let result_id = Some(new_result_id.to_string());
25328            assert_eq!(
25329                params.text_document.uri,
25330                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25331            );
25332            async move {
25333                Ok(lsp::DocumentDiagnosticReportResult::Report(
25334                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
25335                        related_documents: None,
25336                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
25337                            items: Vec::new(),
25338                            result_id,
25339                        },
25340                    }),
25341                ))
25342            }
25343        });
25344
25345    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
25346        project.update(cx, |project, cx| {
25347            let buffer_id = editor
25348                .read(cx)
25349                .buffer()
25350                .read(cx)
25351                .as_singleton()
25352                .expect("created a singleton buffer")
25353                .read(cx)
25354                .remote_id();
25355            let buffer_result_id = project
25356                .lsp_store()
25357                .read(cx)
25358                .result_id(server_id, buffer_id, cx);
25359            assert_eq!(expected, buffer_result_id);
25360        });
25361    };
25362
25363    ensure_result_id(None, cx);
25364    cx.executor().advance_clock(Duration::from_millis(60));
25365    cx.executor().run_until_parked();
25366    assert_eq!(
25367        diagnostic_requests.load(atomic::Ordering::Acquire),
25368        1,
25369        "Opening file should trigger diagnostic request"
25370    );
25371    first_request
25372        .next()
25373        .await
25374        .expect("should have sent the first diagnostics pull request");
25375    ensure_result_id(Some("1".to_string()), cx);
25376
25377    // Editing should trigger diagnostics
25378    editor.update_in(cx, |editor, window, cx| {
25379        editor.handle_input("2", window, cx)
25380    });
25381    cx.executor().advance_clock(Duration::from_millis(60));
25382    cx.executor().run_until_parked();
25383    assert_eq!(
25384        diagnostic_requests.load(atomic::Ordering::Acquire),
25385        2,
25386        "Editing should trigger diagnostic request"
25387    );
25388    ensure_result_id(Some("2".to_string()), cx);
25389
25390    // Moving cursor should not trigger diagnostic request
25391    editor.update_in(cx, |editor, window, cx| {
25392        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25393            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
25394        });
25395    });
25396    cx.executor().advance_clock(Duration::from_millis(60));
25397    cx.executor().run_until_parked();
25398    assert_eq!(
25399        diagnostic_requests.load(atomic::Ordering::Acquire),
25400        2,
25401        "Cursor movement should not trigger diagnostic request"
25402    );
25403    ensure_result_id(Some("2".to_string()), cx);
25404    // Multiple rapid edits should be debounced
25405    for _ in 0..5 {
25406        editor.update_in(cx, |editor, window, cx| {
25407            editor.handle_input("x", window, cx)
25408        });
25409    }
25410    cx.executor().advance_clock(Duration::from_millis(60));
25411    cx.executor().run_until_parked();
25412
25413    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
25414    assert!(
25415        final_requests <= 4,
25416        "Multiple rapid edits should be debounced (got {final_requests} requests)",
25417    );
25418    ensure_result_id(Some(final_requests.to_string()), cx);
25419}
25420
25421#[gpui::test]
25422async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
25423    // Regression test for issue #11671
25424    // Previously, adding a cursor after moving multiple cursors would reset
25425    // the cursor count instead of adding to the existing cursors.
25426    init_test(cx, |_| {});
25427    let mut cx = EditorTestContext::new(cx).await;
25428
25429    // Create a simple buffer with cursor at start
25430    cx.set_state(indoc! {"
25431        ˇaaaa
25432        bbbb
25433        cccc
25434        dddd
25435        eeee
25436        ffff
25437        gggg
25438        hhhh"});
25439
25440    // Add 2 cursors below (so we have 3 total)
25441    cx.update_editor(|editor, window, cx| {
25442        editor.add_selection_below(&Default::default(), window, cx);
25443        editor.add_selection_below(&Default::default(), window, cx);
25444    });
25445
25446    // Verify we have 3 cursors
25447    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
25448    assert_eq!(
25449        initial_count, 3,
25450        "Should have 3 cursors after adding 2 below"
25451    );
25452
25453    // Move down one line
25454    cx.update_editor(|editor, window, cx| {
25455        editor.move_down(&MoveDown, window, cx);
25456    });
25457
25458    // Add another cursor below
25459    cx.update_editor(|editor, window, cx| {
25460        editor.add_selection_below(&Default::default(), window, cx);
25461    });
25462
25463    // Should now have 4 cursors (3 original + 1 new)
25464    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
25465    assert_eq!(
25466        final_count, 4,
25467        "Should have 4 cursors after moving and adding another"
25468    );
25469}
25470
25471#[gpui::test(iterations = 10)]
25472async fn test_document_colors(cx: &mut TestAppContext) {
25473    let expected_color = Rgba {
25474        r: 0.33,
25475        g: 0.33,
25476        b: 0.33,
25477        a: 0.33,
25478    };
25479
25480    init_test(cx, |_| {});
25481
25482    let fs = FakeFs::new(cx.executor());
25483    fs.insert_tree(
25484        path!("/a"),
25485        json!({
25486            "first.rs": "fn main() { let a = 5; }",
25487        }),
25488    )
25489    .await;
25490
25491    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25492    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25493    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25494
25495    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25496    language_registry.add(rust_lang());
25497    let mut fake_servers = language_registry.register_fake_lsp(
25498        "Rust",
25499        FakeLspAdapter {
25500            capabilities: lsp::ServerCapabilities {
25501                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
25502                ..lsp::ServerCapabilities::default()
25503            },
25504            name: "rust-analyzer",
25505            ..FakeLspAdapter::default()
25506        },
25507    );
25508    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
25509        "Rust",
25510        FakeLspAdapter {
25511            capabilities: lsp::ServerCapabilities {
25512                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
25513                ..lsp::ServerCapabilities::default()
25514            },
25515            name: "not-rust-analyzer",
25516            ..FakeLspAdapter::default()
25517        },
25518    );
25519
25520    let editor = workspace
25521        .update(cx, |workspace, window, cx| {
25522            workspace.open_abs_path(
25523                PathBuf::from(path!("/a/first.rs")),
25524                OpenOptions::default(),
25525                window,
25526                cx,
25527            )
25528        })
25529        .unwrap()
25530        .await
25531        .unwrap()
25532        .downcast::<Editor>()
25533        .unwrap();
25534    let fake_language_server = fake_servers.next().await.unwrap();
25535    let fake_language_server_without_capabilities =
25536        fake_servers_without_capabilities.next().await.unwrap();
25537    let requests_made = Arc::new(AtomicUsize::new(0));
25538    let closure_requests_made = Arc::clone(&requests_made);
25539    let mut color_request_handle = fake_language_server
25540        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25541            let requests_made = Arc::clone(&closure_requests_made);
25542            async move {
25543                assert_eq!(
25544                    params.text_document.uri,
25545                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25546                );
25547                requests_made.fetch_add(1, atomic::Ordering::Release);
25548                Ok(vec![
25549                    lsp::ColorInformation {
25550                        range: lsp::Range {
25551                            start: lsp::Position {
25552                                line: 0,
25553                                character: 0,
25554                            },
25555                            end: lsp::Position {
25556                                line: 0,
25557                                character: 1,
25558                            },
25559                        },
25560                        color: lsp::Color {
25561                            red: 0.33,
25562                            green: 0.33,
25563                            blue: 0.33,
25564                            alpha: 0.33,
25565                        },
25566                    },
25567                    lsp::ColorInformation {
25568                        range: lsp::Range {
25569                            start: lsp::Position {
25570                                line: 0,
25571                                character: 0,
25572                            },
25573                            end: lsp::Position {
25574                                line: 0,
25575                                character: 1,
25576                            },
25577                        },
25578                        color: lsp::Color {
25579                            red: 0.33,
25580                            green: 0.33,
25581                            blue: 0.33,
25582                            alpha: 0.33,
25583                        },
25584                    },
25585                ])
25586            }
25587        });
25588
25589    let _handle = fake_language_server_without_capabilities
25590        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
25591            panic!("Should not be called");
25592        });
25593    cx.executor().advance_clock(Duration::from_millis(100));
25594    color_request_handle.next().await.unwrap();
25595    cx.run_until_parked();
25596    assert_eq!(
25597        1,
25598        requests_made.load(atomic::Ordering::Acquire),
25599        "Should query for colors once per editor open"
25600    );
25601    editor.update_in(cx, |editor, _, cx| {
25602        assert_eq!(
25603            vec![expected_color],
25604            extract_color_inlays(editor, cx),
25605            "Should have an initial inlay"
25606        );
25607    });
25608
25609    // opening another file in a split should not influence the LSP query counter
25610    workspace
25611        .update(cx, |workspace, window, cx| {
25612            assert_eq!(
25613                workspace.panes().len(),
25614                1,
25615                "Should have one pane with one editor"
25616            );
25617            workspace.move_item_to_pane_in_direction(
25618                &MoveItemToPaneInDirection {
25619                    direction: SplitDirection::Right,
25620                    focus: false,
25621                    clone: true,
25622                },
25623                window,
25624                cx,
25625            );
25626        })
25627        .unwrap();
25628    cx.run_until_parked();
25629    workspace
25630        .update(cx, |workspace, _, cx| {
25631            let panes = workspace.panes();
25632            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
25633            for pane in panes {
25634                let editor = pane
25635                    .read(cx)
25636                    .active_item()
25637                    .and_then(|item| item.downcast::<Editor>())
25638                    .expect("Should have opened an editor in each split");
25639                let editor_file = editor
25640                    .read(cx)
25641                    .buffer()
25642                    .read(cx)
25643                    .as_singleton()
25644                    .expect("test deals with singleton buffers")
25645                    .read(cx)
25646                    .file()
25647                    .expect("test buffese should have a file")
25648                    .path();
25649                assert_eq!(
25650                    editor_file.as_ref(),
25651                    rel_path("first.rs"),
25652                    "Both editors should be opened for the same file"
25653                )
25654            }
25655        })
25656        .unwrap();
25657
25658    cx.executor().advance_clock(Duration::from_millis(500));
25659    let save = editor.update_in(cx, |editor, window, cx| {
25660        editor.move_to_end(&MoveToEnd, window, cx);
25661        editor.handle_input("dirty", window, cx);
25662        editor.save(
25663            SaveOptions {
25664                format: true,
25665                autosave: true,
25666            },
25667            project.clone(),
25668            window,
25669            cx,
25670        )
25671    });
25672    save.await.unwrap();
25673
25674    color_request_handle.next().await.unwrap();
25675    cx.run_until_parked();
25676    assert_eq!(
25677        3,
25678        requests_made.load(atomic::Ordering::Acquire),
25679        "Should query for colors once per save and once per formatting after save"
25680    );
25681
25682    drop(editor);
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    let close = workspace
25692        .update(cx, |workspace, window, cx| {
25693            workspace.active_pane().update(cx, |pane, cx| {
25694                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25695            })
25696        })
25697        .unwrap();
25698    close.await.unwrap();
25699    assert_eq!(
25700        3,
25701        requests_made.load(atomic::Ordering::Acquire),
25702        "After saving and closing all editors, no extra requests should be made"
25703    );
25704    workspace
25705        .update(cx, |workspace, _, cx| {
25706            assert!(
25707                workspace.active_item(cx).is_none(),
25708                "Should close all editors"
25709            )
25710        })
25711        .unwrap();
25712
25713    workspace
25714        .update(cx, |workspace, window, cx| {
25715            workspace.active_pane().update(cx, |pane, cx| {
25716                pane.navigate_backward(&workspace::GoBack, window, cx);
25717            })
25718        })
25719        .unwrap();
25720    cx.executor().advance_clock(Duration::from_millis(100));
25721    cx.run_until_parked();
25722    let editor = workspace
25723        .update(cx, |workspace, _, cx| {
25724            workspace
25725                .active_item(cx)
25726                .expect("Should have reopened the editor again after navigating back")
25727                .downcast::<Editor>()
25728                .expect("Should be an editor")
25729        })
25730        .unwrap();
25731    color_request_handle.next().await.unwrap();
25732    assert_eq!(
25733        3,
25734        requests_made.load(atomic::Ordering::Acquire),
25735        "Cache should be reused on buffer close and reopen"
25736    );
25737    editor.update(cx, |editor, cx| {
25738        assert_eq!(
25739            vec![expected_color],
25740            extract_color_inlays(editor, cx),
25741            "Should have an initial inlay"
25742        );
25743    });
25744
25745    drop(color_request_handle);
25746    let closure_requests_made = Arc::clone(&requests_made);
25747    let mut empty_color_request_handle = fake_language_server
25748        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25749            let requests_made = Arc::clone(&closure_requests_made);
25750            async move {
25751                assert_eq!(
25752                    params.text_document.uri,
25753                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25754                );
25755                requests_made.fetch_add(1, atomic::Ordering::Release);
25756                Ok(Vec::new())
25757            }
25758        });
25759    let save = editor.update_in(cx, |editor, window, cx| {
25760        editor.move_to_end(&MoveToEnd, window, cx);
25761        editor.handle_input("dirty_again", window, cx);
25762        editor.save(
25763            SaveOptions {
25764                format: false,
25765                autosave: true,
25766            },
25767            project.clone(),
25768            window,
25769            cx,
25770        )
25771    });
25772    save.await.unwrap();
25773
25774    empty_color_request_handle.next().await.unwrap();
25775    cx.run_until_parked();
25776    assert_eq!(
25777        4,
25778        requests_made.load(atomic::Ordering::Acquire),
25779        "Should query for colors once per save only, as formatting was not requested"
25780    );
25781    editor.update(cx, |editor, cx| {
25782        assert_eq!(
25783            Vec::<Rgba>::new(),
25784            extract_color_inlays(editor, cx),
25785            "Should clear all colors when the server returns an empty response"
25786        );
25787    });
25788}
25789
25790#[gpui::test]
25791async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
25792    init_test(cx, |_| {});
25793    let (editor, cx) = cx.add_window_view(Editor::single_line);
25794    editor.update_in(cx, |editor, window, cx| {
25795        editor.set_text("oops\n\nwow\n", window, cx)
25796    });
25797    cx.run_until_parked();
25798    editor.update(cx, |editor, cx| {
25799        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
25800    });
25801    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
25802    cx.run_until_parked();
25803    editor.update(cx, |editor, cx| {
25804        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
25805    });
25806}
25807
25808#[gpui::test]
25809async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
25810    init_test(cx, |_| {});
25811
25812    cx.update(|cx| {
25813        register_project_item::<Editor>(cx);
25814    });
25815
25816    let fs = FakeFs::new(cx.executor());
25817    fs.insert_tree("/root1", json!({})).await;
25818    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
25819        .await;
25820
25821    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
25822    let (workspace, cx) =
25823        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25824
25825    let worktree_id = project.update(cx, |project, cx| {
25826        project.worktrees(cx).next().unwrap().read(cx).id()
25827    });
25828
25829    let handle = workspace
25830        .update_in(cx, |workspace, window, cx| {
25831            let project_path = (worktree_id, rel_path("one.pdf"));
25832            workspace.open_path(project_path, None, true, window, cx)
25833        })
25834        .await
25835        .unwrap();
25836
25837    assert_eq!(
25838        handle.to_any().entity_type(),
25839        TypeId::of::<InvalidBufferView>()
25840    );
25841}
25842
25843#[gpui::test]
25844async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
25845    init_test(cx, |_| {});
25846
25847    let language = Arc::new(Language::new(
25848        LanguageConfig::default(),
25849        Some(tree_sitter_rust::LANGUAGE.into()),
25850    ));
25851
25852    // Test hierarchical sibling navigation
25853    let text = r#"
25854        fn outer() {
25855            if condition {
25856                let a = 1;
25857            }
25858            let b = 2;
25859        }
25860
25861        fn another() {
25862            let c = 3;
25863        }
25864    "#;
25865
25866    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
25867    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25868    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
25869
25870    // Wait for parsing to complete
25871    editor
25872        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
25873        .await;
25874
25875    editor.update_in(cx, |editor, window, cx| {
25876        // Start by selecting "let a = 1;" inside the if block
25877        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25878            s.select_display_ranges([
25879                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
25880            ]);
25881        });
25882
25883        let initial_selection = editor.selections.display_ranges(cx);
25884        assert_eq!(initial_selection.len(), 1, "Should have one selection");
25885
25886        // Test select next sibling - should move up levels to find the next sibling
25887        // Since "let a = 1;" has no siblings in the if block, it should move up
25888        // to find "let b = 2;" which is a sibling of the if block
25889        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
25890        let next_selection = editor.selections.display_ranges(cx);
25891
25892        // Should have a selection and it should be different from the initial
25893        assert_eq!(
25894            next_selection.len(),
25895            1,
25896            "Should have one selection after next"
25897        );
25898        assert_ne!(
25899            next_selection[0], initial_selection[0],
25900            "Next sibling selection should be different"
25901        );
25902
25903        // Test hierarchical navigation by going to the end of the current function
25904        // and trying to navigate to the next function
25905        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25906            s.select_display_ranges([
25907                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
25908            ]);
25909        });
25910
25911        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
25912        let function_next_selection = editor.selections.display_ranges(cx);
25913
25914        // Should move to the next function
25915        assert_eq!(
25916            function_next_selection.len(),
25917            1,
25918            "Should have one selection after function next"
25919        );
25920
25921        // Test select previous sibling navigation
25922        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
25923        let prev_selection = editor.selections.display_ranges(cx);
25924
25925        // Should have a selection and it should be different
25926        assert_eq!(
25927            prev_selection.len(),
25928            1,
25929            "Should have one selection after prev"
25930        );
25931        assert_ne!(
25932            prev_selection[0], function_next_selection[0],
25933            "Previous sibling selection should be different from next"
25934        );
25935    });
25936}
25937
25938#[gpui::test]
25939async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
25940    init_test(cx, |_| {});
25941
25942    let mut cx = EditorTestContext::new(cx).await;
25943    cx.set_state(
25944        "let ˇvariable = 42;
25945let another = variable + 1;
25946let result = variable * 2;",
25947    );
25948
25949    // Set up document highlights manually (simulating LSP response)
25950    cx.update_editor(|editor, _window, cx| {
25951        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
25952
25953        // Create highlights for "variable" occurrences
25954        let highlight_ranges = [
25955            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
25956            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
25957            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
25958        ];
25959
25960        let anchor_ranges: Vec<_> = highlight_ranges
25961            .iter()
25962            .map(|range| range.clone().to_anchors(&buffer_snapshot))
25963            .collect();
25964
25965        editor.highlight_background::<DocumentHighlightRead>(
25966            &anchor_ranges,
25967            |theme| theme.colors().editor_document_highlight_read_background,
25968            cx,
25969        );
25970    });
25971
25972    // Go to next highlight - should move to second "variable"
25973    cx.update_editor(|editor, window, cx| {
25974        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
25975    });
25976    cx.assert_editor_state(
25977        "let variable = 42;
25978let another = ˇvariable + 1;
25979let result = variable * 2;",
25980    );
25981
25982    // Go to next highlight - should move to third "variable"
25983    cx.update_editor(|editor, window, cx| {
25984        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
25985    });
25986    cx.assert_editor_state(
25987        "let variable = 42;
25988let another = variable + 1;
25989let result = ˇvariable * 2;",
25990    );
25991
25992    // Go to next highlight - should stay at third "variable" (no wrap-around)
25993    cx.update_editor(|editor, window, cx| {
25994        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
25995    });
25996    cx.assert_editor_state(
25997        "let variable = 42;
25998let another = variable + 1;
25999let result = ˇvariable * 2;",
26000    );
26001
26002    // Now test going backwards from third position
26003    cx.update_editor(|editor, window, cx| {
26004        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26005    });
26006    cx.assert_editor_state(
26007        "let variable = 42;
26008let another = ˇvariable + 1;
26009let result = variable * 2;",
26010    );
26011
26012    // Go to previous highlight - should move to first "variable"
26013    cx.update_editor(|editor, window, cx| {
26014        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26015    });
26016    cx.assert_editor_state(
26017        "let ˇvariable = 42;
26018let another = variable + 1;
26019let result = variable * 2;",
26020    );
26021
26022    // Go to previous highlight - should stay on first "variable"
26023    cx.update_editor(|editor, window, cx| {
26024        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26025    });
26026    cx.assert_editor_state(
26027        "let ˇvariable = 42;
26028let another = variable + 1;
26029let result = variable * 2;",
26030    );
26031}
26032
26033#[gpui::test]
26034async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
26035    cx: &mut gpui::TestAppContext,
26036) {
26037    init_test(cx, |_| {});
26038
26039    let url = "https://zed.dev";
26040
26041    let markdown_language = Arc::new(Language::new(
26042        LanguageConfig {
26043            name: "Markdown".into(),
26044            ..LanguageConfig::default()
26045        },
26046        None,
26047    ));
26048
26049    let mut cx = EditorTestContext::new(cx).await;
26050    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26051    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
26052
26053    cx.update_editor(|editor, window, cx| {
26054        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26055        editor.paste(&Paste, window, cx);
26056    });
26057
26058    cx.assert_editor_state(&format!(
26059        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
26060    ));
26061}
26062
26063#[gpui::test]
26064async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
26065    cx: &mut gpui::TestAppContext,
26066) {
26067    init_test(cx, |_| {});
26068
26069    let url = "https://zed.dev";
26070
26071    let markdown_language = Arc::new(Language::new(
26072        LanguageConfig {
26073            name: "Markdown".into(),
26074            ..LanguageConfig::default()
26075        },
26076        None,
26077    ));
26078
26079    let mut cx = EditorTestContext::new(cx).await;
26080    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
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.copy(&Copy, window, cx);
26087    });
26088
26089    cx.set_state(&format!(
26090        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
26091    ));
26092
26093    cx.update_editor(|editor, window, cx| {
26094        editor.paste(&Paste, window, cx);
26095    });
26096
26097    cx.assert_editor_state(&format!(
26098        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
26099    ));
26100}
26101
26102#[gpui::test]
26103async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
26104    cx: &mut gpui::TestAppContext,
26105) {
26106    init_test(cx, |_| {});
26107
26108    let url = "https://zed.dev";
26109
26110    let markdown_language = Arc::new(Language::new(
26111        LanguageConfig {
26112            name: "Markdown".into(),
26113            ..LanguageConfig::default()
26114        },
26115        None,
26116    ));
26117
26118    let mut cx = EditorTestContext::new(cx).await;
26119    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26120    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
26121
26122    cx.update_editor(|editor, window, cx| {
26123        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26124        editor.paste(&Paste, window, cx);
26125    });
26126
26127    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
26128}
26129
26130#[gpui::test]
26131async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
26132    cx: &mut gpui::TestAppContext,
26133) {
26134    init_test(cx, |_| {});
26135
26136    let text = "Awesome";
26137
26138    let markdown_language = Arc::new(Language::new(
26139        LanguageConfig {
26140            name: "Markdown".into(),
26141            ..LanguageConfig::default()
26142        },
26143        None,
26144    ));
26145
26146    let mut cx = EditorTestContext::new(cx).await;
26147    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26148    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
26149
26150    cx.update_editor(|editor, window, cx| {
26151        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
26152        editor.paste(&Paste, window, cx);
26153    });
26154
26155    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
26156}
26157
26158#[gpui::test]
26159async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
26160    cx: &mut gpui::TestAppContext,
26161) {
26162    init_test(cx, |_| {});
26163
26164    let url = "https://zed.dev";
26165
26166    let markdown_language = Arc::new(Language::new(
26167        LanguageConfig {
26168            name: "Rust".into(),
26169            ..LanguageConfig::default()
26170        },
26171        None,
26172    ));
26173
26174    let mut cx = EditorTestContext::new(cx).await;
26175    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26176    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
26177
26178    cx.update_editor(|editor, window, cx| {
26179        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26180        editor.paste(&Paste, window, cx);
26181    });
26182
26183    cx.assert_editor_state(&format!(
26184        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
26185    ));
26186}
26187
26188#[gpui::test]
26189async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
26190    cx: &mut TestAppContext,
26191) {
26192    init_test(cx, |_| {});
26193
26194    let url = "https://zed.dev";
26195
26196    let markdown_language = Arc::new(Language::new(
26197        LanguageConfig {
26198            name: "Markdown".into(),
26199            ..LanguageConfig::default()
26200        },
26201        None,
26202    ));
26203
26204    let (editor, cx) = cx.add_window_view(|window, cx| {
26205        let multi_buffer = MultiBuffer::build_multi(
26206            [
26207                ("this will embed -> link", vec![Point::row_range(0..1)]),
26208                ("this will replace -> link", vec![Point::row_range(0..1)]),
26209            ],
26210            cx,
26211        );
26212        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
26213        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26214            s.select_ranges(vec![
26215                Point::new(0, 19)..Point::new(0, 23),
26216                Point::new(1, 21)..Point::new(1, 25),
26217            ])
26218        });
26219        let first_buffer_id = multi_buffer
26220            .read(cx)
26221            .excerpt_buffer_ids()
26222            .into_iter()
26223            .next()
26224            .unwrap();
26225        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
26226        first_buffer.update(cx, |buffer, cx| {
26227            buffer.set_language(Some(markdown_language.clone()), cx);
26228        });
26229
26230        editor
26231    });
26232    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
26233
26234    cx.update_editor(|editor, window, cx| {
26235        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26236        editor.paste(&Paste, window, cx);
26237    });
26238
26239    cx.assert_editor_state(&format!(
26240        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
26241    ));
26242}
26243
26244#[track_caller]
26245fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
26246    editor
26247        .all_inlays(cx)
26248        .into_iter()
26249        .filter_map(|inlay| inlay.get_color())
26250        .map(Rgba::from)
26251        .collect()
26252}